Merge remote-tracking branches 'origin/sub_desktop_1' and 'origin/sub_laptop_1' into main_desktop_1
This commit is contained in:
@@ -0,0 +1,135 @@
|
||||
"""B06 종단에서 넣고 뺀 **계곡 통과 시설**이 세션에 제대로 쌓이는지(2026-09-12 사용자 지시).
|
||||
|
||||
B05·B06 은 한 페이지이므로 관 추가·삭제도 양쪽에서 되어야 한다. 다만 정본에 바로 쓰지
|
||||
않고 **세션에 예약**했다가 [저장]·[확정]에서 한 번에 내보낸다(CLAUDE.md 5장). 넣었다 바로
|
||||
빼면 예약만 걷어 내야 하고, 뺐다 다시 넣어도 마찬가지다 — 그러지 않으면 저장 때 있지도
|
||||
않은 관을 지우거나 같은 자리에 둘이 선다.
|
||||
|
||||
실제 화면 코드를 그대로 컴파일해 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;
|
||||
Module._load = function (request, parent, isMain) {
|
||||
if (request.startsWith("@config/") || request.startsWith("@util/") || request.startsWith("@ui/"))
|
||||
return new Proxy({}, { get: () => () => undefined });
|
||||
return load.call(this, request, parent, isMain);
|
||||
};
|
||||
|
||||
// 세션 저장소 흉내 — 화면 코드는 window.sessionStorage 만 쓴다.
|
||||
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 { createCulvertOptionWriter } = require(process.argv[3]);
|
||||
const EDIT_KEY = "edit";
|
||||
const writer = createCulvertOptionWriter(
|
||||
() => "p1",
|
||||
() => "opt",
|
||||
undefined,
|
||||
() => "move",
|
||||
() => EDIT_KEY,
|
||||
);
|
||||
const read = () => JSON.parse(store.get(EDIT_KEY) ?? '{"adds":[],"removes":[]}');
|
||||
const steps = {};
|
||||
|
||||
writer.queueAdd(120, "pipe");
|
||||
writer.queueAdd(140);
|
||||
steps.afterTwoAdds = read();
|
||||
|
||||
// 방금 넣은 자리를 다시 빼면 **추가 예약만** 사라진다(정본에는 아직 없다).
|
||||
writer.queueRemove(140);
|
||||
steps.afterUndoAdd = read();
|
||||
|
||||
// 정본에 있던 관을 빼면 삭제 예약으로 남는다.
|
||||
writer.queueRemove(200);
|
||||
steps.afterRemove = read();
|
||||
|
||||
// 뺀 자리를 다시 넣으면 삭제 예약만 걷어 낸다(새로 만들지 않는다).
|
||||
writer.queueAdd(200);
|
||||
steps.afterUndoRemove = read();
|
||||
|
||||
// 같은 자리를 두 번 넣어도 하나만 남는다.
|
||||
writer.queueAdd(120);
|
||||
steps.afterDuplicateAdd = read();
|
||||
|
||||
writeFileSync(process.argv[2], JSON.stringify(steps));
|
||||
"""
|
||||
|
||||
|
||||
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:
|
||||
steps = _run(tmp_path)
|
||||
|
||||
# 넣은 둘이 그대로 쌓인다.
|
||||
assert [entry["chainage_m"] for entry in steps["afterTwoAdds"]["adds"]] == [120.0, 140.0]
|
||||
assert steps["afterTwoAdds"]["adds"][0]["facility"] == "pipe"
|
||||
assert steps["afterTwoAdds"]["removes"] == []
|
||||
|
||||
# 방금 넣은 자리를 빼면 추가 예약만 사라지고 삭제 예약은 안 생긴다.
|
||||
assert [entry["chainage_m"] for entry in steps["afterUndoAdd"]["adds"]] == [120.0]
|
||||
assert steps["afterUndoAdd"]["removes"] == []
|
||||
|
||||
# 정본에 있던 관은 삭제 예약으로 남는다.
|
||||
assert steps["afterRemove"]["removes"] == ["200.00"]
|
||||
|
||||
# 뺀 자리를 다시 넣으면 삭제 예약만 걷힌다 — 새 관을 만들지 않는다.
|
||||
assert steps["afterUndoRemove"]["removes"] == []
|
||||
assert [entry["chainage_m"] for entry in steps["afterUndoRemove"]["adds"]] == [120.0]
|
||||
|
||||
# 같은 자리를 두 번 넣어도 하나만 남는다.
|
||||
assert [entry["chainage_m"] for entry in steps["afterDuplicateAdd"]["adds"]] == [120.0]
|
||||
Reference in New Issue
Block a user