"""342-3 배수유역 지문 · 낡음 — 분석 노선 지문이 지금 노선과 다르면 세부 유역을 안 나눔. - 지문: 관 자리 지문은 차례와 무관 · 분석 저장분 지문은 파일이 바뀔 때만 다시 읽음 · 없으면 None. - 낡음: 관 목록이 있으면 관 자리만(세부 유역 없음 · 시설 속성 그대로 · 까닭 글) · 없으면 409. - 같으면 그대로 나눔(_build). """ import asyncio import json import os from pathlib import Path from types import SimpleNamespace from uuid import uuid4 from fastapi.responses import JSONResponse from B04_PreProcess import B04_PreProcess_Router_Basins as basins from common_util import common_util_drainage_pipes as pipes_mod from common_util.common_util_drainage_context import DrainageContext from common_util.common_util_drainage_pipes import PipePoint, pipe_signature, route_signature from common_util.common_util_route_geometry import RouteVertex VERTICES = [RouteVertex(0.0, 0.0, 100.0, 0.0), RouteVertex(300.0, 0.0, 90.0, 300.0)] def _context() -> DrainageContext: return DrainageContext(stored_path="x", project_root=Path("."), vertices=list(VERTICES)) def test_pipe_signature_ignores_order_and_noise(): assert pipe_signature([10.0, 120.0]) == pipe_signature([120.0, 10.001]) assert pipe_signature([10.0, 120.0]) != pipe_signature([10.0, 121.0]) def test_analysis_signature_reads_saved_and_follows_file(tmp_path, monkeypatch): monkeypatch.setattr(pipes_mod, "drainage_dir", lambda stored: tmp_path) assert pipes_mod.analysis_route_signature("x") is None # 저장분 없음 path = tmp_path / "00_watershed_response.json" path.write_text(json.dumps({"status": "success"}), "utf-8") assert pipes_mod.analysis_route_signature("x") is None # 옛 저장분 = 지문 없음 path.write_text(json.dumps({"route_signature": "2-abc"}), "utf-8") os.utime(path, (1, 1)) assert pipes_mod.analysis_route_signature("x") == "2-abc" def _patch(monkeypatch, analysis_signature, stored): async def load(project_id): return _context(), "" monkeypatch.setattr(basins, "load_drainage_context", load) monkeypatch.setattr(basins, "analysis_route_signature", lambda stored_path: analysis_signature) monkeypatch.setattr(basins, "load_pipe_points", lambda *args: stored) built = SimpleNamespace(called=False) def build(stored_path, context, points): built.called = True return SimpleNamespace(pipes=[], basins=["유역"]), points or [] monkeypatch.setattr(basins, "_build", build) monkeypatch.setattr( basins, "_build_cached", lambda context, points: build("x", context, points) ) return built def test_stale_keeps_pipes_without_basins(monkeypatch): stored = [ PipePoint(chainage_m=120.0, source="stream", facility="ford_bridge"), PipePoint(chainage_m=40.0, source="user"), ] built = _patch(monkeypatch, None, stored) # 옛 저장분 context, detail, points, saved, stale = asyncio.run( basins._resolve(uuid4(), None, use_stored=True) ) assert stale and saved and not built.called assert detail.basins == [] assert [point.chainage_m for point in points] == [40.0, 120.0] assert points[1].facility == "ford_bridge" payload = basins._payload(uuid4(), context, detail, points, saved, stale) assert payload["stale"] == basins.STALE_MESSAGE assert payload["route_signature"] == route_signature(VERTICES) assert payload["pipe_signature"] == pipe_signature([40.0, 120.0]) def test_stale_without_pipes_is_409(monkeypatch): _patch(monkeypatch, "다른-노선", None) result = asyncio.run(basins._resolve(uuid4(), None, use_stored=True)) assert isinstance(result, JSONResponse) and result.status_code == 409 def test_same_signature_splits(monkeypatch): built = _patch(monkeypatch, route_signature(VERTICES), None) *_, stale = asyncio.run(basins._resolve(uuid4(), None, use_stored=True)) assert built.called and not stale