- logic 새 파일 9 — B06_Section_Cross_Culvert · _Culvert_Geom · _Culvert_Extra · _Culvert_Tiers · _Culvert_Types · _Culvert_Wire · _Box_Geom · _Ford_Geom · _Ford_Pavement - 그림 파일(UI_Cross_Culvert · _Culvert_Types · _Box · _Ford · _Ford_Pavement)은 받은 좌표로 그리기만 · 관 겹 꼭짓점(pipeShellCorners) · 물넘이 노면 끝 · 월류선 · 유출 성토선은 logic 으로 - 옛 순수 기하 6 파일(UI_Cross_Culvert_Geom · _Extra · _Tiers · _Wire · _Box_Geom · _Ford_Geom) 지움 · 부르는 쪽(B05 Corridor · Structure_Layouts · Route_Carry_Patch · Wall 등)은 import 줄만 - test_472_3 GEOMETRY_PULLS 자기 11 줄 + 더는 안 끌리는 Design_Surface 1 줄 걷음 - 시험 · helper 경로를 새 logic 파일로 — 옮긴 파일을 가리키던 낡은 경로 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
121 lines
5.2 KiB
Python
121 lines
5.2 KiB
Python
"""추가(다단) 기슭막이의 **좌우 이동 하한**이 화면에 알려지는지.
|
|
|
|
왜 (2026-09-07 원인 확정) — 「좌우 이동 1.0m 를 넣어도 벽이 안 움직인다」는 보고가
|
|
다섯 측점 중 넷에서 났다. 지형이 막은 것이 아니었다. 선반 길이가 음수가 되지 않도록
|
|
좌우 이동은 **1.2 × 상하 내림** 아래로 못 내려가는데(`shelfFloor`), 실제 저장값이
|
|
d 2.6m 이면 하한이 3.12m 라 1.0m 요청은 애초에 아무 변화도 못 낸다. 그런데도
|
|
「지형에 막힘」 수치(`shiftBlockedM`)는 0 으로 나와 까닭을 알 길이 없었다.
|
|
|
|
이 시험은 그 하한이 실제로 걸리는지와, 걸렸을 때 `shiftFloorM` 으로 알리는지를 지킨다.
|
|
실제 화면 코드를 그대로 컴파일해 Node 로 돌린다(배수관 세트 거울 시험과 같은 방식).
|
|
"""
|
|
|
|
import json
|
|
import subprocess
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
|
|
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" / "logic/B06_Section_Cross_Culvert_Extra.ts"
|
|
|
|
# 상하 내림 2.0m → 하한 2.4m. 좌우 0.5m 를 요청해도 2.4m 로 밀려 나가야 한다.
|
|
REQUESTED_X = 0.5
|
|
REQUESTED_D = 2.0
|
|
EXPECTED_FLOOR = 2.4
|
|
|
|
_RUNNER = """
|
|
const { buildOutletExtras } = require(process.argv[3]);
|
|
const { writeFileSync } = require("node:fs");
|
|
|
|
// 계류측으로 1:2 로 내려가는 민민한 원지반 — 성토선(1:1.2)보다 완만해 지형이 막을 일이 없다.
|
|
// 시작점은 원지반보다 5m 위 — 그래야 다단이 설 자리(성토부)가 생긴다.
|
|
const groundAt = (offset) => 95 - offset / 2;
|
|
|
|
const result = buildOutletExtras({
|
|
start: { offset: 0, elevation: 100 },
|
|
startBottomElevation: 100,
|
|
outward: 1,
|
|
groundAt,
|
|
limitOffset: 60,
|
|
adjusts: [{ x: %(x)s, d: %(d)s, h: null, m: null }],
|
|
});
|
|
|
|
writeFileSync(process.argv[2], JSON.stringify({
|
|
count: result.walls.length,
|
|
appliedX: result.appliedAdjusts[0]?.x ?? null,
|
|
floor: result.walls[0]?.shiftFloorM ?? null,
|
|
blocked: result.walls[0]?.shiftBlockedM ?? null,
|
|
}));
|
|
"""
|
|
|
|
|
|
def _run(tmp_path: Path, requested_x: float = REQUESTED_X) -> dict:
|
|
out = tmp_path / "out"
|
|
subprocess.run( # noqa: S603 — 고정 실행 파일
|
|
[
|
|
"node",
|
|
str(TSC),
|
|
"--ignoreConfig",
|
|
"--target",
|
|
"es2022",
|
|
# 프로젝트 소스는 확장자 없는 상대 경로를 쓴다(번들러 기준) — CommonJS 로 옮겨야
|
|
# Node 가 그대로 찾아 준다. 러너도 `.cjs` 라 `type` 설정과 무관하게 CJS 로 돈다.
|
|
"--module",
|
|
"commonjs",
|
|
"--skipLibCheck",
|
|
"--outDir",
|
|
str(out),
|
|
str(MODULE),
|
|
],
|
|
cwd=str(PROJECT_ROOT),
|
|
# 딸려 오는 화면 모듈에 별칭 경로(`@config/…`)가 있어 형 검사는 실패한다 —
|
|
# 그래도 JS 는 나온다. 형 검사는 `npm run typecheck` 몫이고, 여기서는 **돌려 보는** 것이 목적.
|
|
check=False,
|
|
capture_output=True,
|
|
)
|
|
compiled = next(out.rglob("B06_Section_Cross_Culvert_Extra.js"), None)
|
|
assert compiled is not None, "화면 코드가 JS 로 안 나옴 — tsc 실패"
|
|
(out / "runner.cjs").write_text(
|
|
_RUNNER % {"x": requested_x, "d": REQUESTED_D}, 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"))
|
|
|
|
|
|
@pytest.mark.skipif(not TSC.is_file(), reason="프론트엔드 의존성 미설치")
|
|
def test_좌우_이동은_상하_내림의_1_2배까지_따라_나간다(tmp_path: Path) -> None:
|
|
produced = _run(tmp_path)
|
|
assert produced["count"] >= 1, "시험용 자리에서 단이 하나도 안 섰음"
|
|
assert produced["appliedX"] == pytest.approx(EXPECTED_FLOOR, abs=0.05), (
|
|
f"요청 {REQUESTED_X}m 가 하한 {EXPECTED_FLOOR}m 로 안 밀림 — {produced['appliedX']}"
|
|
)
|
|
|
|
|
|
@pytest.mark.skipif(not TSC.is_file(), reason="프론트엔드 의존성 미설치")
|
|
def test_하한에_눌린_사실을_알린다(tmp_path: Path) -> None:
|
|
"""요청이 통째로 무시됐으면 그 까닭이 남아야 한다 — 화면이 툴팁으로 읽는다."""
|
|
produced = _run(tmp_path)
|
|
assert produced["floor"] == pytest.approx(EXPECTED_FLOOR, abs=0.05)
|
|
# 지형이 막은 것이 아니므로 「지형에 막힘」은 뜨면 안 된다 — 두 까닭이 섞이면 오해한다.
|
|
assert not produced["blocked"], "지형 탓으로 잘못 알림"
|
|
|
|
|
|
@pytest.mark.skipif(not TSC.is_file(), reason="프론트엔드 의존성 미설치")
|
|
def test_요청이_없으면_알리지_않는다(tmp_path: Path) -> None:
|
|
"""기본 자리(좌우 0)에서는 하한이 걸려도 알릴 것이 없다 — 모든 벽에 뜨면 소리만 된다."""
|
|
produced = _run(tmp_path, requested_x=0.0)
|
|
assert produced["count"] >= 1
|
|
assert not produced["floor"], "손대지 않은 벽에도 하한을 알림"
|