"""PLAN 472-5 남음 (요구 272 · 273) — 암 경계 · 암 절토 경사 값 주인 = 세션 칸 하나 · 메모리 맵 걷음(N5) · 노선 저장 뒤 옛 노선 칸만 비움. 못박는 것 - 암 경계(▲▼ · ↺) · 암 경사를 고친 뒤 ↶ 하고 다른 측점을 고쳐도 되돌린 값이 되살아나지 않음 — 옛 코드는 메모리 맵(`offsets` · `ratios`)이 옛 값을 쥐고 다음 조작이 칸을 통째로 다시 써 ↶ 를 덮었음(옛 코드 실패). - ↶ 뒤 카드 읽기 = 조작 전 값(저장분 그대로) · ↷ = 다시 그 값 · 저장 재료(`offsets()` · `ratios()`)도 세션 칸 그대로. - `clearRouteDrafts` = 그 노선 번호의 노선 범위 초안만 비움(다른 노선 · 다른 프로젝트 · 프로젝트 칸 그대로). - 노선 바뀐 [저장] 성공 뒤 옛 노선 칸 · 초안 전 hold 비움 · 실패면 그대로(`design_save.ts`). ./venv/Scripts/python.exe -m pytest resources/tester/test_472_5r_rock_owner.py -q """ from __future__ import annotations import json import shutil import subprocess from pathlib import Path import pytest HERE = Path(__file__).resolve().parent ROOT = HERE.parents[1] pytestmark = pytest.mark.skipif( not (ROOT / "config" / "node_modules" / "vite").is_dir(), reason="프론트엔드 의존성 미설치", ) P = "p4725rock" @pytest.fixture(scope="module") def out(tmp_path_factory) -> dict: work = tmp_path_factory.mktemp("s4725r") subprocess.run( # noqa: S603 — 고정 실행 파일 [ "node", str(ROOT / "config" / "node_modules" / "vite" / "bin" / "vite.js"), "build", "--configLoader", "runner", "--ssr", "../resources/tester/helper_472_5r_rock_entry.ts", "--outDir", str(work), "--logLevel", "error", ], cwd=ROOT, check=True, capture_output=True, timeout=300, ) bundle = work / "bundle.mjs" shutil.copy(work / "helper_472_5r_rock_entry.js", bundle) proc = subprocess.run( # noqa: S603 ["node", str(HERE / "helper_472_5r_rock.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) def test_조작_A_는_세션_칸에(out) -> None: a = out["afterA"] assert a["rockb"] == {"100.00": -0.9, "120.00": -0.4} assert a["cutslope"] == {"140.00": 0.7} r = out["readsA"] assert r["100"]["rock"] == -0.9 and r["120"]["rock"] == -0.4 and r["140"]["cut"] == 0.7 def test_되돌리면_칸이_비고_카드도_조작_전(out) -> None: assert out["afterUndo"] == {"rockb": {}, "cutslope": {}} r = out["readsUndo"] assert r["100"] == {"rock": -0.8, "cut": 0.4} # 저장분 그대로 assert r["120"] == {"rock": -0.5, "cut": 0.4} assert r["140"] == {"rock": -0.5, "cut": 0.4} def test_되돌린_뒤_다른_조작이_되돌림을_안_덮음(out) -> None: """N5 — 옛 코드는 메모리 맵이 A 값을 쥐고 B 를 쓸 때 통째로 다시 씀(옛 코드 실패).""" assert out["afterB"] == {"rockb": {"140.00": -0.6}, "cutslope": {"100.00": 0.5}} assert out["payloadB"] == {"rock": {"140.00": -0.6}, "cut": {"100.00": 0.5}} def test_다시하기_뒤_값_같음(out) -> None: assert out["afterUndo2"] == {"rockb": {}, "cutslope": {}} assert out["afterRedo"] == out["afterB"] r = out["readsRedo"] assert r["100"] == {"rock": -0.8, "cut": 0.5} assert r["140"] == {"rock": -0.6, "cut": 0.4} def test_되돌림_단추_기본값_표준(out) -> None: assert out["afterReset"] == {"rockb": {"140.00": -0.5}, "cutslope": {"100.00": 0}} r = out["readsReset"] assert r["140"]["rock"] == -0.5 and r["100"]["cut"] == 0.4 def test_옛_노선_칸만_비움(out) -> None: before, after = set(out["keysBefore"]), set(out["keysAfter"]) gone = before - after assert gone == { f"aislo:draft:rockb:{P}:7", f"aislo:draft:cutslope:{P}:7", f"aislo:draft:crossw:{P}:7", } assert f"aislo:draft:rockb:{P}:8" in after assert "aislo:draft:rockb:other:7" in after assert f"aislo:draft:uphill:{P}" in after def test_노선_저장_뒤_옛_노선_칸_비움(tmp_path) -> None: proc = subprocess.run( # noqa: S603 ["node", str(HERE / "helper_472_5r_route_save.cjs")], cwd=ROOT, capture_output=True, text=True, encoding="utf-8", timeout=120, ) assert proc.returncode == 0, proc.stderr got = json.loads(proc.stdout) # 새 노선 번호(9) ≠ 옛(3) — 다 저장하고 페이지를 다시 세운 뒤 옛 노선 칸 · hold 비움 assert got["moved"]["done"] is True assert got["moved"]["calls"][-3:] == [ "rebuild", "clearRouteDrafts:p1:3", "clearState:route-draft-hold:p1", ] # 같은 번호면 노선 칸은 안 지움(새 노선 칸일 수 있음) · hold 만 assert "clearRouteDrafts:p1:3" not in got["same"]["calls"] assert got["same"]["calls"][-1] == "clearState:route-draft-hold:p1" # 서버 실패 = 초안 그대로 assert got["failed"]["done"] is False assert not [c for c in got["failed"]["calls"] if c.startswith("clear")]