"""364-4 노선 추천 미리 셈 — B05 열 때 예약 · 겹쳐 셈 막음 · 모달 진행 표시. ./venv/Scripts/python.exe -m pytest resources/tester/test_364_4_precompute_on_open.py -q 옛 프로젝트 · 판이 바뀐 저장본은 사용자가 처음 누를 때 기다렸음(요구 239 · 토공 62.9 s). `GET /route/plan`(B05 열기)이 미리 셈을 예약 · 도는 동안 또 오면 끝난 뒤 한 번 더 · 진행은 `GET /route/initial/progress` · 모달 상태줄이 그것을 붙임. """ import asyncio from pathlib import Path from uuid import uuid4 from B05_Profile import B05_Profile_Router_RouteInitial as router ROOT = Path(__file__).resolve().parents[2] def test_겹쳐_셈_막음_끝난_뒤_한_번_더(monkeypatch) -> None: calls: list[str] = [] seen: list[dict] = [] pid = uuid4() async def precompute(project_id): calls.append(str(project_id)) progress = router._RUNNING[str(project_id)] progress.update(total=5, done=2, mode="earthwork") seen.append(router.precompute_progress(project_id)) await asyncio.sleep(0.05) monkeypatch.setattr(router, "precompute_defaults", precompute) async def main() -> None: first = asyncio.create_task(router.run_precompute(pid)) await asyncio.sleep(0.01) # 도는 중 두 번 더 와도 셈은 한 번 더만 await router.run_precompute(pid) await router.run_precompute(pid) await first asyncio.run(main()) assert calls == [str(pid), str(pid)] assert seen[0] == {"running": True, "done": 2, "total": 5, "mode": "earthwork"} assert router.precompute_progress(pid) == { "running": False, "done": 0, "total": 0, "mode": None, } def test_실패해도_진행_비움(monkeypatch) -> None: async def broken(_pid): raise RuntimeError("셈 실패") monkeypatch.setattr(router, "precompute_defaults", broken) pid = uuid4() try: asyncio.run(router.run_precompute(pid)) except RuntimeError: pass assert not router.precompute_progress(pid)["running"] def test_b05_열기_예약_모달_진행_배선() -> None: replan = (ROOT / "B05_Profile/B05_Profile_Router_Replan.py").read_text(encoding="utf-8") plan = replan[replan.index("async def read_route_plan") :] assert "schedule_precompute(project_id)" in plan[: plan.index("\n@router")] source = (ROOT / "B05_Profile/B05_Profile_Router_RouteInitial.py").read_text(encoding="utf-8") assert '"/{project_id}/route/initial/progress"' in source # 체인 · B01 범위도 같은 한 줄(run_precompute)로 assert "run_precompute(project_id), task_name" in source assert "await run_precompute(project_id)" in source api = (ROOT / "B05_Profile/B05_Profile_Api_RouteInitial.ts").read_text(encoding="utf-8") assert "/route/initial/progress" in api initial = (ROOT / "B05_Profile/B05_Profile_UI_RouteEdit_Initial.ts").read_text(encoding="utf-8") assert "progress: () => fetchPrecomputeProgress(projectId)" in initial modal = (ROOT / "B05_Profile/B05_Profile_UI_RouteEdit_Recommend.ts").read_text(encoding="utf-8") assert "미리 셈하는 중 ${p.done}/${p.total}" in modal def test_모달이_여는_갈래_먼저(monkeypatch) -> None: """저장된 갈래(추적 아님)를 먼저 셈 — 모달이 열자마자 그 갈래를 물음.""" from B05_Profile.B05_Profile_Engine_RouteInitial_Store import DEFAULT_MODES asked: list[str] = [] async def paths(_pid): return (ROOT, None) async def compute(_pid, request): asked.append(request.mode) return {} monkeypatch.setattr(router.replan, "_project_paths", paths) monkeypatch.setattr( router, "read_initial_choice", lambda _root: {"logic": "gentle", "weights": None, "criteria": None}, ) monkeypatch.setattr(router, "compute_initial", compute) asyncio.run(router.precompute_defaults(uuid4())) assert asked[0] == "gentle" and asked[-1] == "follow" assert sorted(asked) == sorted(DEFAULT_MODES)