"""원지반 잠금 풀기(54-45) — 흙막이 자리 · 다단 · 높이를 원지반이 막지 않음. 남는 규칙: 벽 윗면 노면 위 금지(d ≥ 0) · 관 보호공 흙막이 m 단위 자리(관 길이 정수). 서버 면적은 같은 TS 를 Node 로 돌림(`B06_Section_Server_Calc_Node.ts`) — 번들도 같은 판인지 봄. """ import json import re import subprocess from pathlib import Path import pytest PROJECT_ROOT = Path(__file__).resolve().parents[2] B06 = PROJECT_ROOT / "B06_Section" TSC = PROJECT_ROOT / "config" / "node_modules" / "typescript" / "bin" / "tsc" BUNDLE = PROJECT_ROOT / "config" / "server_calc_node" / "B06_Section_Server_Calc_Node.js" _RUNNER = """ import { writeFileSync } from "node:fs"; import { placePipeWall } from "./B06_Section_UI_Cross_Culvert_Solve.js"; import { buildOutletExtras } from "./B06_Section_UI_Cross_Culvert_Extra.js"; const pipe = (d, x) => placePipeWall({ autoOffset: 10, outward: 1, height: 2, baseElevation0: 98, edgeElevation: 100, invertCap: Number.POSITIVE_INFINITY, adjust: { x, d, h: null, m: null }, defaultD: 4.8, }); const extras = (ground, d) => buildOutletExtras({ start: { offset: 10, elevation: 95 }, startBottomElevation: 94.5, outward: 1, groundAt: () => ground, limitOffset: 40, adjusts: [{ x: 0, d, h: 2, m: null }, { x: 0, d, h: 2, m: null }], }); const buried = extras(100, 1); const high = extras(80, 3); writeFileSync(process.argv[2], JSON.stringify({ deep: pipe(6, 2), up: pipe(-5, 0), buriedCount: buried.walls.length, buriedD: buried.appliedAdjusts.map((a) => a.d), highCount: high.walls.length, highD: high.appliedAdjusts.map((a) => a.d), })); """ @pytest.mark.skipif(not TSC.is_file(), reason="프론트엔드 의존성 미설치") def test_원지반이_자리_다단을_막지_않고_노면_위만_막음(tmp_path: Path) -> None: out = tmp_path / "js" for name in ("Culvert_Solve", "Culvert_Extra"): subprocess.run( # noqa: S603 — 고정 실행 파일 [ "node", str(TSC), str(B06 / f"B06_Section_UI_Cross_{name}.ts"), "--outDir", str(out), "--module", "esnext", "--target", "es2022", "--moduleResolution", "bundler", "--skipLibCheck", "--ignoreConfig", ], cwd=str(PROJECT_ROOT), capture_output=True, ) for emitted in out.rglob("*.js"): emitted.write_text( re.sub(r'(from\s+"\./[^"]+)"', r'\1.js"', emitted.read_text(encoding="utf-8")), encoding="utf-8", ) base = next(path.parent for path in out.rglob("B06_Section_UI_Cross_Culvert_Solve.js")) (base / "runner.mjs").write_text(_RUNNER, encoding="utf-8") result = tmp_path / "out.json" subprocess.run( # noqa: S603 ["node", str(base / "runner.mjs"), str(result)], check=True, capture_output=True ) got = json.loads(result.read_text(encoding="utf-8")) assert (got["deep"]["d"], got["deep"]["x"]) == (6, 2), "관 흙막이 깊이 · 좌우가 되돌아감" assert got["up"]["d"] == pytest.approx(0), "관 흙막이 윗면이 노면 위로 올라감" assert got["up"]["topElevation"] == pytest.approx(100), "노면 멈춤 자리 틀림" assert got["buriedCount"] == 2, "원지반에 묻힌 다단이 끊김" assert got["buriedD"] == [1, 1], "묻힌 다단의 높이가 되돌아감" assert got["highCount"] == 2, "원지반 위 다단이 끊김" assert got["highD"] == [3, 3], "뜬 다단의 높이가 되돌아감" def test_화면_서버_같은_판() -> None: for name in ("Culvert_Solve", "Culvert_Geom", "Revetment"): source = (B06 / f"B06_Section_UI_Cross_{name}.ts").read_text(encoding="utf-8") assert "requireCrossing" not in source, f"{name} 에 매몰 자리 금지가 남음" assert "outletReceivable" not in source, f"{name} 에 유출 수용 가드가 남음" assert "const placeable" not in (B06 / "B06_Section_UI_Cross_Revetment.ts").read_text( encoding="utf-8" ), "사면안정 흙막이 · 옹벽 매몰 자리 금지가 남음" assert "const placeable" not in (B06 / "B06_Section_UI_Cross_Culvert_Extra.ts").read_text( encoding="utf-8" ), "추가 흙막이 매몰 자리 금지가 남음" bundle = BUNDLE.read_text(encoding="utf-8") assert "outletReceivable" not in bundle and "requireCrossing" not in bundle, ( "서버 번들이 옛 판 — npm run build:server-calc" )