"""509-5 분석 지형 고르기 = 추천 노선 창 안 — 지형마다 추천 미리 셈 · 창 지형 바꿈 = 그 지형 저장본 · 없으면 안내 + [재계산] · [적용] = 창 지형이 설계 지형 · 평면 고르기 없음. ./venv/Scripts/python.exe -m pytest resources/tester/test_509_5_recommend_terrain.py -q 가짜 세계(`test_224_3` wired) — 지형마다 다른 격자(수치지형도 = 1 m 높임)라 저장 키가 갈림. """ import asyncio import json from contextlib import asynccontextmanager from pathlib import Path from uuid import uuid4 import pytest from test_224_3_recommend_store import STREAMS, _ask, _terrain, wired # noqa: F401 — 픽스처 from B05_Profile import B05_Profile_Router_RouteInitial as router from B05_Profile import B05_Profile_Router_Terrain as terrain_router from B05_Profile.B05_Profile_Engine_RouteInitial_Store import DEFAULT_MODES, store_dir ROOT = Path(__file__).resolve().parents[2] @pytest.fixture def two(wired, monkeypatch): # noqa: F811 """라이다 설계 · 수치지형도 고를 수 있음 — 지형마다 다른 격자.""" import B05_Profile.B05_Profile_Engine_RouteInitial as engine import common_util.common_util_surface_confirmation as surface async def terrains(_pid, _root): return ["las", "sheet"] monkeypatch.setattr(router, "precompute_terrains", terrains) monkeypatch.setattr(surface, "analysis_terrain_params", lambda s, t: {**s, "t": t}) monkeypatch.setattr( engine, "load_terrain_and_streams", lambda _root, selection: (_terrain(1.0 if selection.get("t") == "sheet" else 0.0), STREAMS), ) return wired def _saved(root: Path) -> list[dict]: return [json.loads(p.read_text(encoding="utf-8")) for p in store_dir(root).glob("*.json")] def test_미리_셈이_지형마다_추천_다섯(two) -> None: asyncio.run(router.precompute_defaults(uuid4())) assert two.count == 2 * len(DEFAULT_MODES) saved = _saved(two.root) assert sorted({item["request"]["terrain"] for item in saved}) == ["las", "sheet"] assert all(item["default"] for item in saved) # 창 지형 바꿈 = 그 지형 저장본(셈 없음) · 두 지형 답이 따로 for mode in DEFAULT_MODES: assert _ask(False, mode=mode, terrain="sheet")["stored"] is True assert _ask(False, mode=mode, terrain="las")["stored"] is True assert two.count == 2 * len(DEFAULT_MODES) def test_미리_셈_없는_지형은_안내_재계산은_셈(two) -> None: """기존 프로젝트(수치지형도 미리 셈 없음) — 안내 한 줄 · [재계산]이면 그 자리 셈.""" missing = _ask(False, mode="gentle", terrain="sheet") assert missing.status_code == 409 message = json.loads(missing.body)["message"] assert "전처리를 다시 하면" in message and "[재계산]" in message assert two.count == 0 assert _ask(False, mode="gentle", terrain="sheet", recompute=True)["stored"] is False assert two.count == 1 assert _ask(False, mode="gentle", terrain="sheet")["stored"] is True def test_미리_셈_지형_차례_설계_먼저(monkeypatch) -> None: import common_util.common_util_surface_confirmation as surface class _Pool: @asynccontextmanager async def acquire(self): yield None async def params(_c, _pid): return {"source_filter": surface.SHEET_ANALYSIS_FILTER} async def choosable(_c, _pid, _params, _dir): return ["las", "sheet"] monkeypatch.setattr(router, "get_db_pool", lambda: _Pool()) monkeypatch.setattr(surface, "get_surface_confirmation_params", params) monkeypatch.setattr(terrain_router, "choosable_terrains", choosable) assert asyncio.run(router.precompute_terrains(uuid4(), ROOT)) == ["sheet", "las"] def test_고를_지형은_DB_모델_있을_때만(tmp_path, monkeypatch) -> None: import B04_PreProcess.B04_PreProcess_Service as service from common_util.common_util_surface_confirmation import SHEET_ANALYSIS_FILTER (tmp_path / f"dtm_{SHEET_ANALYSIS_FILTER}.npz").write_bytes(b"") params = {"source_filter": "classification", "method": "dtm", "smooth": False} found: list[str] = [] async def missing(_c, _pid, selection): found.append(selection["source_filter"]) raise LookupError async def present(_c, _pid, _selection): return 7 monkeypatch.setattr(service, "find_surface_model_for_selection", missing) pick = terrain_router.choosable_terrains assert asyncio.run(pick(None, uuid4(), params, tmp_path)) == ["las"] assert found == [SHEET_ANALYSIS_FILTER] monkeypatch.setattr(service, "find_surface_model_for_selection", present) assert asyncio.run(pick(None, uuid4(), params, tmp_path)) == ["las", "sheet"] def test_지형_바꿈은_추천_적용만_초기값_다시_세움() -> None: replan = (ROOT / "B05_Profile/B05_Profile_Router_Replan.py").read_text(encoding="utf-8") save = replan.split("async def _save_route(", 1)[1] assert "request.terrain if as_initial else None," in save # 손 [저장]은 지형 안 바꿈 assert save.index("await _recompute(") < save.index("refresh_initial_snapshot(project_id") recompute = replan.split("async def _recompute(", 1)[1].split("\nasync def ")[0] assert "switch_design_terrain(connection, project_id, terrain)" in recompute def _ts(name: str) -> str: return (ROOT / "B05_Profile" / name).read_text(encoding="utf-8") def test_창_지형_고르기_배선() -> None: modal = _ts("B05_Profile_UI_RouteEdit_Recommend.ts") assert 'data-act="terrain"' in modal and "data-terrain-row hidden" in modal assert 'params.terrains.includes("las") && params.terrains.includes("sheet")' in modal change = modal.split('terrainSelect.addEventListener("change"', 1)[1][:300] assert "void run();" in change # 바꾸면 그 지형 저장본(없으면 안내 · [재계산]) assert "recompute,\n terrain,\n" in modal # 서버에 창 지형 initial = _ts("B05_Profile_UI_RouteEdit_Initial.ts") assert "terrain: params.terrains()?.design" in initial # 처음 = 설계 지형 assert "return { ...normalizeRouteInitial(" in initial and "terrain };" in initial assert "picked.terrain ?? null," in initial # [적용] = 창 지형 api = _ts("B05_Profile_Api_RouteInitial.ts") assert "terrain?: AnalysisTerrain;" in api and "body: JSON.stringify(request)," in api def test_평면_지형_고르기_없음_이름만() -> None: chrome = _ts("B05_Profile_UI_RouteEdit_Chrome.ts") main = _ts("B05_Profile_UI_RouteEdit.ts") assert 'data-act="terrain"' not in chrome and 'data-slot="terrain-name"' in chrome assert "terrainName.textContent = `지형 ${" in main assert "terrainSelect" not in main and "setAnalysisTerrain" not in main assert "routeedit-terrain" not in (ROOT / "A00_Common/b_page_state.ts").read_text( encoding="utf-8" ) pipeline = (ROOT / "A00_Common/design/design_pipeline_page.ts").read_text(encoding="utf-8") assert "terrain: null }; // 설계 지형(509-5)" in pipeline assert len(pipeline.splitlines()) <= 700 and len(main.splitlines()) <= 700