"""횡단 벽 형상 한 벌 — 관 유입·유출 흙막이 · 추가 흙막이 · 사면안정 흙막이 · 옹벽. 규칙(10-6 사용자): ① 네 벽 모두 공용 `buildRevetWallGeometry` 로 선다 — 형태는 안 무늬만 바꿈(외곽 같음). ② 형태별 높이 한도(메 2.0 · 찰 3.0 · 콘크리트 5.0) · 원지반 맞춤 자동 자르기 없음. ③ 벽 윗면은 노면 위로 올라오지 않음(넘으면 노면에서 멈춤). 서버 면적은 같은 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" _RUNNER = """ import { writeFileSync } from "node:fs"; import { buildRevetWallGeometry } from "./B06_Section_UI_Cross_Wall.js"; import { revetWallSpec, REVET_FORMS } from "./B06_Section_UI_Cross_Culvert_Const.js"; const anchor = { offset: 5, elevation: 100 }; const outline = (form, role, extra = {}) => buildRevetWallGeometry({ anchor, outward: 1, height: 2.5, material: "dry", role, form, ...extra }) .points.map((p) => [p.offset, p.elevation]); writeFileSync(process.argv[2], JSON.stringify({ forms: REVET_FORMS.map((form) => outline(form, "outlet")), roles: ["inlet", "outlet", "extra"].map((role) => outline("돌쌓기(메)", role)), capped: buildRevetWallGeometry({ anchor, outward: 1, height: 5, material: "dry", role: "outlet", roadElevation: 103, }).topJoint.elevation, under: buildRevetWallGeometry({ anchor, outward: 1, height: 2, material: "dry", role: "outlet", roadElevation: 103, }).topJoint.elevation, dry: revetWallSpec({ revet_form: "돌쌓기(메)" }, { h: 3.0 }, true, 0.3).pureHeight, concrete: revetWallSpec({ revet_form: "콘크리트" }, { h: 7.5 }, true, 0.3).pureHeight, })); """ @pytest.mark.skipif(not TSC.is_file(), reason="프론트엔드 의존성 미설치") def test_벽_외곽_한_벌_높이_입력_그대로_노면_멈춤(tmp_path: Path) -> None: out = tmp_path / "js" # 형(type) import 가 끌어오는 페이지 파일은 별칭을 못 풀어 오류를 내지만 산출물은 나옴. subprocess.run( # noqa: S603 — 고정 실행 파일 [ "node", str(TSC), str(B06 / "B06_Section_UI_Cross_Wall.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_Wall.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 all(outline == got["forms"][0] for outline in got["forms"]), "형태마다 외곽이 다름" assert all(outline == got["roles"][0] for outline in got["roles"]), "벽 갈래마다 외곽이 다름" assert got["capped"] == pytest.approx(103.0), "노면 위로 올라옴" assert got["under"] == pytest.approx(102.0), "노면 아래 벽이 잘림" assert got["dry"] == pytest.approx(3.0), "돌쌓기(메) 3.0 이 잘림" assert got["concrete"] == pytest.approx(7.5), "콘크리트 7.5 가 잘림" def test_네_벽이_공용_함수로_서고_형태_한도가_없다() -> None: for name in ("Culvert_Geom", "Culvert_Extra", "Revetment"): source = (B06 / f"B06_Section_UI_Cross_{name}.ts").read_text(encoding="utf-8") assert "buildRevetWallGeometry(" in source, f"{name} 가 공용 벽 함수를 안 씀" for path in B06.glob("*.ts"): source = path.read_text(encoding="utf-8") assert "revetHeightLimit" not in source, f"{path.name} 에 형태 높이 한도가 남음" assert "materialLimit" not in source, f"{path.name} 에 재질 높이 한도가 남음" extra = (B06 / "B06_Section_UI_Cross_Culvert_Extra.ts").read_text(encoding="utf-8") assert "fitted" not in extra, "추가 흙막이가 원지반에 맞춰 늘어남"