Files
Aislo/resources/tester/test_547_1_project_summary.py
T
eomsangdonandClaude Opus 5.5 8b1b286e04 feat(B01): 프로젝트 카드 요약 함수 · API — 단계 · 썸네일 선 · 담당 · 멈춘 날 · 연장 · 측점 · 구조물 (PLAN 547-1)
- B01_Dashboard_Summary.project_summaries(ids) — 쿼리 다섯 번(개수 무관) · 넣은 차례 · 없는 값은 칸 뺌
- 썸네일 = 최신 노선 GeoJSON 중심선 100 점 안쪽 · 0~1 칸(가로세로 비 유지 · y 아래로)
- GET /api/dashboard/projects/summary?ids= — 보이는 프로젝트만(시스템 관리자 전부 · 회사 · 내 것)
- 실측 21 개 0.14 s

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-26 18:01:25 +09:00

144 lines
5.0 KiB
Python

"""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"]