- 원인: 화면 attach · 서버 carry_facility_attributes 가 거리 제한 없이 가장 가까운 관에 속성을 붙여, 관 수 > 속성 수이거나 관을 이웃 쪽으로 옮기면 BOX · 세월교가 옆 관 측점으로 넘어감 - 고침: 화면 carryIndices ↔ 서버 carry_indices 짝 — 허용 오차 0.05m 안 가장 가까운 원본만 · 한 원본 한 번 · 못 붙인 관은 속성 없음(추천은 기존대로 빈칸에만) · 두 파일 머리에 짝 표기 - 관을 옮기면 속성이 따라감: 지도 끌기(onMove → move) · 되돌리기 · 구조물 폼(follow — 사라진 관 하나 · 새 관 하나일 때) - B06 옛 「관 이동 예약」(culvertmove): 저장 때 읽지도 적용하지도 않음 — flushCulvertOptions · createCulvertOptionWriter 에서 이동 키 제거 - 시험: 거울 시험 test_b05_facility_carry(화면 · 서버 같은 답 · 끌기 · 되돌리기) · 옛 최근접 · 이동 예약 적용 시험을 새 규칙으로 고침 - 확인: ORCA 5180 랩탑_보조 — 세월교(173.09) 8m 앞 165m 에 옵션 없는 관 추가 → 저장 → pipe_points.json 165 = 속성 없음 · 173.09 = ford_bridge · 나머지 11건 전후 같음 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RrQ65VtGZbae2VKgYVMhmc
135 lines
4.8 KiB
Python
135 lines
4.8 KiB
Python
"""관 편집이 **한 자리에만** 쌓이는지(2026-09-12 사용자: 담는 꼴도 한 벌이어야 한다).
|
|
|
|
B05 는 관 목록 전체를 세션 스냅샷(`pipes`)으로 담고, B06 은 바뀐 것만 따로 담고 있었다.
|
|
저장 순서에 따라 한쪽이 다른 쪽을 덮어 **넣은 관이 사라지는** 일이 생겼다. 그래서 관
|
|
목록은 두 페이지가 같은 스냅샷을 쓰도록 합쳤다.
|
|
|
|
여기서 지키는 것은 둘이다.
|
|
① 저장기(`createCulvertOptionWriter`)에는 **관을 넣고 빼는 길이 없다** — 남아 있으면
|
|
관 목록을 담는 자리가 다시 둘이 된다.
|
|
② 옛 세션에 남아 있던 **이동 예약은 저장 때 안 씀** — 관 위치는 스냅샷으로만 옮김
|
|
(적용하면 관이 이웃 측점으로 튀어 BOX · 세월교 속성이 넘어감).
|
|
|
|
실제 화면 코드를 그대로 컴파일해 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);
|
|
|
|
// ① 관을 넣고 빼는 길이 저장기에 남아 있으면 안 된다.
|
|
const methods = Object.keys(writer).sort();
|
|
|
|
// ② 옛 세션의 이동 예약은 남아 있어도 안 씀 — 구간값만 제 측점에 나감.
|
|
store.set("move", JSON.stringify({ "120.00": 135 }));
|
|
store.set("opt", JSON.stringify({ "120.00": { pipe_count: 2 } }));
|
|
|
|
api
|
|
.flushCulvertOptions("p1", "opt")
|
|
.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"] == [120, 200]
|