Files
Aislo/resources/tester/test_547_1_project_summary.py
T
2026-10-30 14:37:39 +09:00

145 lines
5.1 KiB
Python

"""547-1 프로젝트 카드 요약 — 썸네일 줄임 · 단계 · 없는 값 빼기 · 보이는 프로젝트만."""
import asyncio
import json
from datetime import timedelta
import B01_Dashboard.B01_Dashboard_Router_Home as home
import B01_Dashboard.B01_Dashboard_Summary as summary
from common_util.common_util_time import utc_now_naive
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] == "예정노선" # 591-2 기준 지정 걷음
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 = utc_now_naive() # DB 값 = UTC
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"]