Files
Aislo/resources/tester/test_b06_culvert_edit_queue.py
T
eomsangdonandClaude Opus 5 b1ba37b004 refactor(b06): 관 목록을 담는 자리를 한 벌로 합침
B05 는 관 목록 전체를 세션 스냅샷으로, B06 은 바뀐 것만 따로 담고 있어 담는 꼴이 둘이었음.
저장 순서를 맞춰 결과는 같게 했으나 자리가 둘인 한 같은 사고가 다시 남.

B06 의 추가·삭제·이동을 모두 B05 가 쓰는 스냅샷(pipes)에 쓰도록 바꾸고, B06 전용 예약
경로(culvertedit 세션·queueAdd·queueRemove·queueMove)를 걷어 냄. 옛 세션에 남아 있던
이동 예약만 저장 때 한 번 흘려보내게 읽기를 남김.

이제 관을 넣고 빼고 옮기는 길이 좌측 폼·목록·종단 우클릭·측점선 끌기 모두 한 함수이고,
담기는 자리도 한 곳임.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Fe1QWPTfw11PaKh2LjwXdR
2026-09-12 16:16:19 +09:00

140 lines
4.8 KiB
Python

"""관 편집이 **한 자리에만** 쌓이는지(2026-09-12 사용자: 담는 꼴도 한 벌이어야 한다).
B05 는 관 목록 전체를 세션 스냅샷(`pipes`)으로 담고, B06 은 바뀐 것만 따로 담고 있었다.
저장 순서에 따라 한쪽이 다른 쪽을 덮어 **넣은 관이 사라지는** 일이 생겼다. 그래서 관
목록은 두 페이지가 같은 스냅샷을 쓰도록 합쳤다.
여기서 지키는 것은 둘이다.
① 저장기(`createCulvertOptionWriter`)에는 **관을 넣고 빼는 길이 없다** — 남아 있으면
관 목록을 담는 자리가 다시 둘이 된다.
② 옛 세션에 남아 있던 **이동 예약은 저장 때 흘려보낸다** — 합치기 전에 만들어 둔
예약이 조용히 사라지면 안 된다.
실제 화면 코드를 그대로 컴파일해 Node 로 돌린다(다단 하한 시험과 같은 방식).
"""
import json
import subprocess
import sys
from pathlib import Path
PROJECT_ROOT = Path(__file__).resolve().parents[2]
if str(PROJECT_ROOT) not in sys.path:
sys.path.insert(0, str(PROJECT_ROOT))
TSC = PROJECT_ROOT / "config" / "node_modules" / "typescript" / "bin" / "tsc"
MODULE = PROJECT_ROOT / "B06_Section" / "B06_Section_Api_Culvert_Options.ts"
_RUNNER = """
const { writeFileSync } = require("node:fs");
const Module = require("node:module");
// 화면 코드가 번들러 별칭(`@config/…`)을 쓴다 — Node 에는 없으니 빈 껍데기로 돌려준다.
const load = Module._load;
const saved = [];
Module._load = function (request, parent, isMain) {
if (request.startsWith("@config/") || request.startsWith("@util/") || request.startsWith("@ui/"))
return new Proxy({}, { get: () => () => undefined });
const mod = load.call(this, request, parent, isMain);
// 관 정본 읽기·쓰기는 서버로 나가므로 가로챈다.
if (request.includes("B04_PreProcess_Api_Fetch")) {
return {
...mod,
fetchDetailPipePoints: async () => ({
pipe_points: [
{ chainage_m: 120, lonlat: [0, 0], source: "stream" },
{ chainage_m: 200, lonlat: [0, 0], source: "stream" },
],
}),
saveDetailPipePoints: async (_projectId, points) => saved.push(points),
};
}
return mod;
};
const store = new Map();
global.window = {
sessionStorage: {
getItem: (key) => (store.has(key) ? store.get(key) : null),
setItem: (key, value) => store.set(key, String(value)),
removeItem: (key) => store.delete(key),
},
};
const api = require(process.argv[3]);
const writer = api.createCulvertOptionWriter(
() => "p1",
() => "opt",
undefined,
() => "move",
);
// ① 관을 넣고 빼는 길이 저장기에 남아 있으면 안 된다.
const methods = Object.keys(writer).sort();
// ② 합치기 전 세션에 남아 있던 이동 예약은 저장 때 흘려보낸다.
store.set("move", JSON.stringify({ "120.00": 135 }));
api
.flushCulvertOptions("p1", "opt", "move")
.then(() => {
writeFileSync(
process.argv[2],
JSON.stringify({
methods,
savedChainages: (saved[0] ?? []).map((point) => point.chainage_m),
moveLeft: store.get("move") ?? null,
}),
);
})
.catch((error) => {
writeFileSync(process.argv[2], JSON.stringify({ error: String(error) }));
process.exitCode = 1;
});
"""
def _run(tmp_path: Path) -> dict:
out = tmp_path / "out"
subprocess.run( # noqa: S603 — 고정 실행 파일
[
"node",
str(TSC),
"--ignoreConfig",
"--target",
"es2022",
"--module",
"commonjs",
"--skipLibCheck",
"--outDir",
str(out),
str(MODULE),
],
cwd=str(PROJECT_ROOT),
check=False,
capture_output=True,
)
compiled = next(out.rglob("B06_Section_Api_Culvert_Options.js"), None)
assert compiled is not None, "화면 코드가 JS 로 안 나옴 — tsc 실패"
(out / "runner.cjs").write_text(_RUNNER, encoding="utf-8")
result = tmp_path / "result.json"
subprocess.run( # noqa: S603 — 고정 실행 파일
["node", str(out / "runner.cjs"), str(result), str(compiled)],
cwd=str(PROJECT_ROOT),
check=True,
capture_output=True,
)
return json.loads(result.read_text(encoding="utf-8"))
def test_관_목록을_담는_자리는_하나뿐이다(tmp_path: Path) -> None:
result = _run(tmp_path)
assert "error" not in result, result.get("error")
# ① 저장기에는 구간값 예약과 내보내기만 있다 — 관을 넣고 빼는 길은 스냅샷 한 곳뿐이다.
assert result["methods"] == ["flush", "queue"]
# ② 옛 세션의 이동 예약은 저장 때 반영되고 비워진다.
assert result["savedChainages"] == [135, 200]
assert result["moveLeft"] is None