# -*- coding: utf-8 -*- """PLAN 415 — 한 페이지 끊김 ① (분석 412 3장 H1 · H2 · M4). 못박는 것 - H1 관 · 구조물만 옮김 = 「노선 바뀜」 넘김 규칙 안 탐 — 측점 열쇠째 옮김(단면 유형 · 사면 쉬프트 · 소단 깊이 · 관 측점 고침 그대로 · 두 번 옮겨도) · 노선 입력이 바뀐 때만 넘김 규칙(대조). - H2 [저장] 도중 계산 줄 먹이기 멈춤 — 관 초안이 비어도 열 때 읽은 옛 관 목록으로 안 돎 · 맞춤 고리도 쉼 · 저장 끝 = 저장 목록 다시 읽음 · 새 자리 그대로(다시 셈 0). - 415-3 누가거리 조절(옹벽 · 세월 · BOX · 집수 · 연동)도 관 새 자리로 — 조정창 맵이 안 선 페이지도. - 415-2 관 옮김 → 되돌리기 = 묶음 입력 · 공용 측점도 원래 · 다시하기 = 옮긴 자리. - M4 새 시설 측점 서버 미리보기 걷음 — 미리보기 요청 0(`test_273_2` · `test_318` 이 맡음) · 여기는 코드 자리. 짝: `helper_415_swap.mjs`(346-2 진입점 묶음 · 진짜 상세 가게) · `helper_415_h2.cjs`(진짜 계산 묶음 · 365 틀). """ from __future__ import annotations import json import shutil import subprocess import sys import tempfile from pathlib import Path import pytest HERE = Path(__file__).resolve().parent ROOT = HERE.parents[1] if str(HERE) not in sys.path: sys.path.insert(0, str(HERE)) import test_365_page_pipeline as t365 # noqa: E402 pytestmark = t365.pytestmark @pytest.fixture(scope="module") def h1(tmp_path_factory) -> dict: work = tmp_path_factory.mktemp("b415") build = subprocess.run( # noqa: S603 — 고정 실행 파일 [ "node", str(ROOT / "config" / "node_modules" / "vite" / "bin" / "vite.js"), "build", "--configLoader", "runner", "--ssr", "../resources/tester/helper_346_2_swap_entry.ts", "--outDir", str(work), "--logLevel", "error", ], cwd=ROOT, capture_output=True, text=True, encoding="utf-8", timeout=300, ) assert build.returncode == 0, build.stderr bundle = work / "bundle.mjs" shutil.copy(work / "helper_346_2_swap_entry.js", bundle) proc = subprocess.run( # noqa: S603 — 고정 실행 파일 ["node", str(HERE / "helper_415_swap.mjs"), str(bundle)], cwd=ROOT, capture_output=True, text=True, encoding="utf-8", timeout=120, ) assert proc.returncode == 0, proc.stderr return json.loads(proc.stdout) @pytest.fixture(scope="module") def h2() -> dict: with tempfile.TemporaryDirectory() as folder: path, grid = t365._job(Path(folder)) proc = subprocess.run( # noqa: S603 — 고정 실행 파일 ["node", str(HERE / "helper_415_h2.cjs"), str(path), str(grid)], capture_output=True, text=True, encoding="utf-8", timeout=180, ) assert proc.returncode == 0, proc.stderr return json.loads(proc.stdout) def test_H1_순수_관만_옮김은_노선_바뀜_아님(h1): assert h1["pure"] == {"pipeOnly": False, "route": True} def test_H1_관_옮김_뒤_고침_그대로(h1): before = h1["before"] assert before["choices"] == [[40, "soil", "right_cut"], [45.05, "soil", "left_cut"]] assert before["shoulder"] == [["grid:1", {"left": 0.7}]] assert before["berm"] == [["grid:3", {"right": 0.4}]] moved = h1["pipeMoved"] # 관 측점 고침은 새 자리(50 m)로 · 격자 고침 그대로 · 「노선 밖 고침」 알림 없음 assert moved["choices"] == [[40, "soil", "right_cut"], [50, "soil", "left_cut"]] assert moved["shoulder"] == before["shoulder"] and moved["berm"] == before["berm"] assert moved["toasts"] == 0 again = h1["pipeMovedAgain"] assert again["choices"] == [[40, "soil", "right_cut"], [55, "soil", "left_cut"]] assert again["shoulder"] == before["shoulder"] and again["berm"] == before["berm"] def test_H1_노선이_바뀌면_넘김_규칙(h1): routed = h1["routeMoved"] # 대조 — 노선 입력이 바뀐 때 = 넘김 규칙(단면 유형 버림 · 지반 유형 · 사면 쉬프트 · 소단 깊이 넘김 = 417) assert routed["choices"] == [[40, "soil", None], [55, "soil", None]] assert routed["shoulder"] == [["grid:1", {"left": 0.7}]] assert routed["berm"] == [["grid:3", {"right": 0.4}]] def test_H2_저장_도중_먹이기_멈춤(h2): opened = h2["open"] assert h2["moved"]["input"] == [c + 5 for c in opened] during = h2["during"] assert during["runs"] == 0 and during["rounds"] == 0 # 옛 관 목록으로 안 돎 · 고리도 쉼 assert during["input"] == h2["moved"]["input"] assert during["stations"] == h2["moved"]["stations"] assert during["reads"] == 0 def test_H2_저장_끝_저장_목록_다시_읽음(h2): after = h2["after"] assert after["reads"] >= 1 # 저장 목록 다시 읽음 assert after["input"] == h2["moved"]["input"] # 새 자리 그대로 assert after["stations"] == h2["moved"]["stations"] # 관 · 구조물 다시 먹이기 없음(옛 목록으로 안 돎) — 저장 측점 기준이 새 자리라 횡단만 한 번 assert all(source == ["cross"] for source in after["sources"]) and after["runs"] <= 1 assert h2["reconcile"] == 0 def test_M4_새_시설_측점_미리보기_걷음(): drafts = (ROOT / "B06_Section/B06_Section_Section_Drafts.ts").read_text(encoding="utf-8") assert "previewStructureSections" not in drafts page = (ROOT / "A00_Common/design/design_pipeline_page.ts").read_text(encoding="utf-8") guard = (ROOT / "A00_Common/design/design_pipeline_facilities.ts").read_text(encoding="utf-8") # 저장 창구는 늦게 불러옴 — 묶음(시험 · 화면)이 저장 모듈을 맨 위에서 안 끎 assert 'from "./design_save"' not in page and 'import("./design_save")' in guard def test_415_2_관_옮김_되돌리기_측점_원래(h2): undo = h2["undo"] base = h2["moved"]["input"] # 저장 뒤 저장분 = 옮긴 자리 assert undo["movedAgain"]["input"] == [c + 5 for c in base] # 되돌리기 = 세션만 되씀(가게 알림 없음) → 묶음 입력 · 공용 측점도 원래 assert undo["undone"]["draft"] is None assert undo["undone"]["input"] == base and undo["undone"]["stations"] == base assert undo["redone"]["input"] == undo["movedAgain"]["input"] assert undo["redone"]["stations"] == undo["movedAgain"]["stations"] def test_415_3_누가거리_조절_관_새_자리로(h1): # 조정창 맵이 안 선 페이지(B05 만 · 새로고침)도 관 측점 누가거리 조절이 새 자리로 · 격자 값 그대로 adjusts = h1["adjusts"] assert adjusts["revetx"] == {"50.00:outlet": {"x": 0.3}, "20.00:inlet": {"x": 0.1}} assert adjusts["fordadjust"] == {"50.00": {"w": 1}} assert adjusts["revetlink"] == {"detached": {"50.00": True}, "grade": {}}