"""547-1 프로젝트 카드 요약 — 썸네일 줄임 · 단계 · 없는 값 빼기 · 보이는 프로젝트만.""" import asyncio import json from datetime import datetime, timedelta import B01_Dashboard.B01_Dashboard_Router_Home as home import B01_Dashboard.B01_Dashboard_Summary as summary ROUTE = "B05_Profile/route/r.geojson" def test_thumb_line_fits_unit_box_and_caps_points(): line = [[1000.0 + i, 2000.0 + i * 0.5] for i in range(500)] thumb = summary.thumb_line(line) assert len(thumb) == 100 assert thumb[0] == [0.0, 0.75] and thumb[-1] == [1.0, 0.25] # y 아래로 · 세로 가운데 assert all(0 <= x <= 1 and 0 <= y <= 1 for x, y in thumb) assert summary.thumb_line([[0.0, 0.0]]) is None def test_stage_names_follow_step_bar(): assert len(summary.stage_names("ROAD")) == 7 assert summary.stage_names("PLAN")[0] == "기준 지정" assert summary.stage_names("EROSION") == summary.stage_names("ROAD") class _Cursor: def __init__(self, db): self.db, self.rows = db, [] async def __aenter__(self): return self async def __aexit__(self, *_): return False async def execute(self, sql, args=()): self.db["queries"] += 1 key = next(k for k in self.db["answers"] if k in sql) self.rows = self.db["answers"][key] async def fetchall(self): return self.rows class _Pool: def __init__(self, db): self.db = db def acquire(self): pool = self class _Conn: async def __aenter__(self): return self async def __aexit__(self, *_): return False def cursor(self, *_): return _Cursor(pool.db) return _Conn() def test_summaries_in_batch(monkeypatch, tmp_path): now = datetime.now() route_dir = tmp_path / "B05_Profile" / "route" route_dir.mkdir(parents=True) coords = [[0, 0, 1], [10, 0, 1], [10, 5, 1]] (route_dir / "r.geojson").write_text(json.dumps({"geometry": {"coordinates": coords}})) (route_dir / "structures.json").write_text(json.dumps({"structures": [{}, {}]})) db = { "queries": 0, "answers": { "FROM projects p LEFT JOIN users": [ { "id": "a", "name": "가", "project_kind": "ROAD", "region": "강원", "updated_at": now - timedelta(days=20), "storage_path": "storage/a", "company_id": 1, "user_id": 7, "owner_name": "엄", }, { "id": "b", "name": "나", "project_kind": "PLAN", "region": None, "updated_at": now, "storage_path": None, "company_id": 1, "user_id": 7, "owner_name": "엄", }, ], "project_workflow_stages": [ {"project_id": "a", "stage_no": n, "stage_key": "", "state": s} | {"progress_percent": 0, "params": None, "message": None} | {"started_at": None, "completed_at": None} for n, s in enumerate(["COMPLETE", "COMPLETE", "IN_PROGRESS"] + ["NOT"] * 4) ], "project_members": [{"project_id": "a", "name": "박"}], "FROM routes": [ {"id": 9, "project_id": "a", "total_length_m": 15.04, "route_data_path": ROUTE}, {"id": 5, "project_id": "a", "total_length_m": 1.0, "route_data_path": "old"}, ], "cross_sections": [{"route_id": 9, "n": 3}], }, } monkeypatch.setattr(summary, "get_db_pool", lambda: _Pool(db)) monkeypatch.setattr(summary, "project_root", lambda p: tmp_path if p else None) out = asyncio.run(summary.project_summaries(["b", "a", "zz"])) assert [s["id"] for s in out] == ["b", "a"] # 넣은 차례 · 없는 id 빠짐 b, a = out assert (a["stage_index"], a["stage_total"], a["stage_name"]) == (2, 7, "노선설계") assert a["stale_days"] == 20 and a["owner_names"] == ["엄", "박"] assert a["metrics"] == {"length_m": 15.0, "stations": 3, "structures": 2} assert len(a["thumb"]) == 3 and a["done"] is False assert b["thumb"] is None and b["metrics"] == {} and b["stage_name"] == "기준 지정" assert db["queries"] == 5 def test_summary_api_drops_invisible_ids(monkeypatch): seen = {} async def fake_visible(session): return ["a", "b"] async def fake_summaries(ids): seen["ids"] = ids return [] monkeypatch.setattr(home, "visible_project_ids", fake_visible) monkeypatch.setattr(home, "project_summaries", fake_summaries) asyncio.run(home.projects_summary(ids="b, x,a,b", session={})) assert seen["ids"] == ["b", "a"] asyncio.run(home.projects_summary(ids=None, session={})) assert seen["ids"] == ["a", "b"]