"""요구 280-3 — 노선 초안 중 ↶ · ↷ = 관 · 구조물 · 추가 측점도 그 걸음 사진(묶음 입력까지). · 옛 흠 — ↶ 차례 follow 가 계획고 사진만 내고 시설은 안 내 묶음 측점이 마지막 끌기 누가거리에 남음 (구조물 없는 구조물 측점 · 측점 없는 구조물) · 끌기 사이 손 옮김이 있으면 맞춤도 건너뛰어 오래 남음 · 잼 — ↶ · ↷ 첫 차례 · 조용해진 뒤 묶음 측점 시설 · 초안 상세 비정규 측점 = 측점 계획(가게 값) · 유령 0 · 빠짐 0 · 계획고 사진(356-4 ▲)은 그대로 짝: `helper_280_3_route_undo_facilities.cjs`(판 = 356-4 · 실제 TS 를 Node 로 · 화면 부품 · 서버 창구만 가짜). """ from __future__ import annotations import json import subprocess import tempfile from pathlib import Path import pytest import test_349_0q_undo_echo as base HERE = Path(__file__).resolve().parent MODES = ("structure", "pipe", "plus", "none") def build_case(folder: Path) -> tuple[dict, Path]: """349-0q 합성 노선 · 단면에 구조물 둘(점 · 구간)을 얹은 입력.""" job, grid = base.build_case(folder) sections = job["detail"]["cross_sections"] picks = [max(1, len(sections) * n // 6) for n in (1, 2, 3, 4, 5)] pipes = [ {"id": f"p{n}", "source": "manual", "facility": "pipe", **base._between(sections, n)} for n in picks[:2] ] point = base._between(sections, picks[2]) a, b = base._between(sections, picks[3]), base._between(sections, picks[4]) job.update( pipes=pipes, structures=[ { "structure_id": "s-2803a", "type_id": "cross_underdrain", "placement": "point", **point, }, { "structure_id": "s-2803b", "type_id": "retaining_wall", "placement": "interval", "start_m": a["chainage_m"], "chainage_m": round((a["chainage_m"] + b["chainage_m"]) / 2, 2), "end_m": b["chainage_m"], "x": (a["x"] + b["x"]) / 2, "y": (a["y"] + b["y"]) / 2, }, ], touched={}, ) return job, grid def _run(job: dict, grid_path: Path, mode: str) -> dict: with tempfile.TemporaryDirectory() as folder: path = Path(folder) / "job.json" path.write_text(json.dumps(job), encoding="utf-8") grid = Path(folder) / "grid.bin" grid.write_bytes(base.dtm_grid_bytes(grid_path)) proc = subprocess.run( # noqa: S603 — 고정 실행 파일 [ "node", str(HERE / "helper_280_3_route_undo_facilities.cjs"), str(path), str(grid), mode, ], capture_output=True, text=True, encoding="utf-8", timeout=300, ) assert proc.returncode == 0, proc.stderr return json.loads(proc.stdout) @pytest.fixture(scope="module") def runs() -> dict: with tempfile.TemporaryDirectory() as folder: job, grid = build_case(Path(folder)) # 추가 측점 — 끌면 밀리는 뒤쪽 두 측점 사이 sections = job["detail"]["cross_sections"] job["plusAt"] = (sections[-5]["chainage_m"] + sections[-4]["chainage_m"]) / 2 return {mode: _run(job, grid, mode) for mode in MODES} def _clean(m: dict) -> None: assert m["ghost"] == [] and m["missing"] == [], m assert m["detailGhost"] in ([], None) and m["detailMissing"] in ([], None), m @pytest.mark.parametrize("mode", MODES) def test_되돌리기_다시하기_시설_측점_같음(runs, mode): r = runs[mode] assert r["moved"]["want"] != r["drag2"]["want"] or mode == "none" # 끌기 2 가 시설을 옮김 for key in ("undo", "redo"): _clean(r[key]["settled"]) _clean(r[key]["first"]) # 맞춤 한 바퀴 전 첫 차례부터 assert r[key]["settled"]["detailGhost"] is not None # 노선 초안 그대로(초안 상세를 잼) assert r["undo"]["settled"]["want"] == r["moved"]["want"] # ↶ = 끌기 2 전 시설 자리 assert r["redo"]["settled"]["want"] == r["drag2"]["want"] assert r["blocked"] == 0 @pytest.mark.parametrize("mode", MODES) def test_끌기_손옮김_사이_측점_같음(runs, mode): r = runs[mode] for key in ("open", "drag1", "lift", "moved", "drag2"): _clean(r[key]) def test_계획고_사진_그대로(runs): r = runs["structure"] assert r["undo"]["settled"]["offs"] == r["moved"]["offs"] # ▲ 남음(356-4) assert r["redo"]["settled"]["offs"] == r["drag2"]["offs"] @pytest.fixture(scope="module") def frames() -> dict: proc = subprocess.run( # noqa: S603 — 고정 실행 파일 ["node", str(HERE / "helper_280_3_draft_frame.cjs")], capture_output=True, text=True, encoding="utf-8", timeout=120, ) assert proc.returncode == 0, proc.stderr return json.loads(proc.stdout) def test_옛_틀_상세는_저장분으로_맞춤(frames): r = frames["old_old"] # 새로고침 첫 화면 · 묶음 갈아끼움 전 assert r["hidden"] == [] and r["preview"] == [] assert r["names"] == {"100": "맹암거", "153": "옹벽", "160": "옹벽", "167": "옹벽"} old = frames["old_null"] assert ( old["hidden"] and "180" in old["names"] ) # 옛 동작 = 숨김 · 남의 자리 이름(시험이 흠을 잼) def test_새_틀_상세는_저장분_종류와_안_견줌(frames): r = frames["new_new"] # 묶음 갈아끼움 뒤 assert r["hidden"] == [] and r["preview"] == [] assert r["names"]["120"] == "맹암거" assert frames["new_null"]["preview"] == [120] # 옛 동작 = 구조물 없는 사본