"""387-7(384 단계 7) 결과 층 — 세션 상세 = 저장 정본만 · 열 때 세션 상세 JSON.parse 한 번. · 상세 가게(새로고침)와 묶음 cross 입력이 어느 쪽이 먼저 읽어도 세션 글은 한 번만 풂(`_Section_Canon`) · 정본 사본은 캐시 객체와 따로 — 다시 셈 · 초안 얹기가 캐시를 고쳐도 묶음 입력의 정본은 그대로 · 갈아끼움 · 되돌림은 세션 상세를 안 씀(`test_365_page_pipeline` 365-1 · 이 파일은 소스로) ./venv/Scripts/python.exe -m pytest resources/tester/test_384_7_section_canon.py -q """ from __future__ import annotations import json import re import subprocess from pathlib import Path ROOT = Path(__file__).resolve().parents[2] LOADER = Path(__file__).resolve().parent / "helper_ts_cjs_load.cjs" DRIVER = r""" const data = new Map(); const fake = { get length() { return data.size; }, key: (i) => [...data.keys()][i] ?? null, getItem: (k) => (data.has(k) ? data.get(k) : null), setItem: (k, v) => data.set(k, String(v)), removeItem: (k) => data.delete(k), }; globalThis.window = { sessionStorage: fake, localStorage: fake }; const { loadTs } = require(process.argv[1]); const canon = loadTs(process.argv[2]); const state = loadTs(process.argv[3]); const detail = { longitudinal: {}, cross_sections: [ { chainage_m: 0, design: { ground_type: "soil", cut_area_m2: 1 } }, { chainage_m: 20, design: { ground_type: "rock", cut_area_m2: 2 } }, ], }; let parses = 0; const parse = JSON.parse; JSON.parse = (text, ...rest) => { if (typeof text === "string" && text.includes("cross_sections")) parses += 1; return parse(text, ...rest); }; const out = {}; // 묶음 입력이 먼저 → 상세 가게 state.writeState("section-detail", detail, "p", 1); const rows = canon.savedDesigns("p", 1); const shown = canon.sessionDetail("p", 1); out.cross = { parses, rows: rows.length }; // 정본 사본 = 캐시 객체와 따로 shown.cross_sections[0].design.cut_area_m2 = 99; out.canonKept = canon.savedDesigns("p", 1)[0].design.cut_area_m2; // 상세 가게가 먼저 → 묶음 입력 parses = 0; state.writeState("section-detail", detail, "p", 2); canon.sessionDetail("p", 2); canon.savedDesigns("p", 2); out.storeFirst = parses; // 버림 → 다시 받기 전엔 정본 없음 canon.forgetCanon("p"); state.writeState("section-detail", null, "p", 1); out.forgot = canon.savedDesigns("p", 1); process.stdout.write(JSON.stringify(out)); """ def _run() -> dict: done = subprocess.run( # noqa: S603 — 고정 실행 파일 [ "node", "-e", DRIVER, str(LOADER), str(ROOT / "B06_Section" / "B06_Section_Section_Canon.ts"), str(ROOT / "A00_Common" / "b_page_state.ts"), ], capture_output=True, text=True, encoding="utf-8", cwd=str(ROOT), ) assert done.returncode == 0, done.stderr return json.loads(done.stdout) def test_열_때_세션_상세_한_번만_풂() -> None: out = _run() assert out["cross"] == {"parses": 1, "rows": 2} assert out["storeFirst"] == 1 assert out["canonKept"] == 1 # 캐시를 고쳐도 정본 사본 그대로 assert out["forgot"] is None def test_세션_상세_쓰는_곳_받을_때_하나() -> None: store = (ROOT / "B06_Section" / "B06_Section_Section_Store.ts").read_text(encoding="utf-8") code = re.sub(r"/\*.*?\*/|//[^\n]*", "", store, flags=re.S) assert code.count('writeState("section-detail"') == 1 # 서버에서 받은 정본만 cross = (ROOT / "A00_Common" / "design" / "design_pipeline_cross.ts").read_text( encoding="utf-8" ) assert "section-detail" not in re.sub(r"/\*.*?\*/|//[^\n]*", "", cross, flags=re.S)