Files
Aislo/resources/tester/test_266_card_toggle_mismatch.py
T
eomsangdonandClaude Opus 5.5 97b5a78695 fix(441 B05 스냅 끌기): 맞춤 교점 이동 한도 = 간격 · 미리보기 측점 밖 늘면 그 걸음 되돌림 · 초안 없는 측점 단계 = 저장 스냅
- StationFit reach = intervalM(반 간격이면 MC 18+9 ~ 19+1 못 맞춤)
- 끌기 · 방향키 — 미리보기 측점 밖 곡선 수가 누를 때보다 늘면 그 걸음 되돌림 · dragMoved 는 막힘 검사 뒤(433-14)
- 계산 줄 stations 단계 · follow 새 측점 — 초안 없으면 저장 노선 station_snap · station_snap_step(snapOptions)
- 시험 — 390_5 끌기 훑기(놓은 뒤 측점 밖 0 · 자동 · 고정 R) · 344_3 저장 스냅 · 266 사면 이동 한 걸음 · 다시 그림 한 번

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ji4nuyLVHcxrXWHTdb5nRD
2026-10-18 18:42:06 +09:00

140 lines
5.5 KiB
Python

"""PLAN 439 — B06 카드 머리 줄 단추가 저장 노선에서 늘 반영됨(요구 266).
못박는 것
- 저장 노선 cross 차례(`refreshSavedCross`) = 카드 자료(상세 설계)를 **늘** 다시 셈 —
복합경사 · 측구 · 지반 단추마다 그 측점 설계선 · 면적이 바뀜.
- 묶음 측점 = 상세 측점이면 돌려줌 · 1 mm 넘게 다르면 null(묶음 엔진) — 그래도 카드 자료는 고침.
- 사면 이동 한 걸음도 같음 · 걸음마다 받는 쪽(카드 · 종단 · 유토 다시 그림) 한 번(441 · 요구 268-2 · 4).
- 서버 폴백(선형 저장분 없음)에도 카드 선택이 실림(열쇠 = 측점 자리) · 서버는 저장분 위에 얹음.
짝: `helper_266_card_toggle.mjs` · 진입점 `helper_266_card_toggle_entry.ts` ·
지형 · 노선 틀 = `test_346_2_pipeline_cross_page.py`.
"""
from __future__ import annotations
import json
import shutil
import subprocess
import sys
import tempfile
from pathlib import Path
import pytest
HERE = Path(__file__).resolve().parent
ROOT = HERE.parents[1]
for path in (ROOT, HERE):
if str(path) not in sys.path:
sys.path.insert(0, str(path))
from test_346_2_pipeline_cross_page import _fake, _line, _sections # noqa: E402
from B06_Section.B06_Section_Router import _with_design_choices # noqa: E402
from common_util.common_util_surface_sampler import DtmGridSampler # noqa: E402
from config.config_system import STANDARD_ROCK_BOUNDARY_DEFAULT_OFFSET_M # noqa: E402
from config.config_system_design import STANDARD_CROSS_SECTION # noqa: E402
pytestmark = pytest.mark.skipif(
not (ROOT / "config" / "node_modules" / "vite").is_dir(),
reason="프론트엔드 의존성 미설치",
)
CUT_DEPTH_M = 3.0
@pytest.fixture(scope="module")
def r(tmp_path_factory) -> dict:
work = tmp_path_factory.mktemp("b439")
build = subprocess.run( # noqa: S603 — 고정 실행 파일
[
"node",
str(ROOT / "config" / "node_modules" / "vite" / "bin" / "vite.js"),
"build",
"--configLoader",
"runner",
"--ssr",
"../resources/tester/helper_266_card_toggle_entry.ts",
"--outDir",
str(work),
"--logLevel",
"error",
],
cwd=ROOT,
capture_output=True,
text=True,
encoding="utf-8",
timeout=300,
)
assert build.returncode == 0, build.stderr
bundle = work / "bundle.mjs"
shutil.copy(work / "helper_266_card_toggle_entry.js", bundle)
with tempfile.TemporaryDirectory() as folder:
npz, nodes = _fake(Path(folder))
sampler = DtmGridSampler.from_npz(npz)
rows = _sections(_line(nodes), sampler, [])["cross_sections"]
middle = len(rows) // 2
picked = rows[middle - 1 : middle + 2]
sections = [{**s, "plan_z": float(s["center_z"]) - CUT_DEPTH_M} for s in picked]
job = {
"sections": sections,
"target": 1,
"mode": f"{picked[1].get('uphill_side') or 'left'}_cut",
"standard": STANDARD_CROSS_SECTION,
"rockDefault": STANDARD_ROCK_BOUNDARY_DEFAULT_OFFSET_M,
}
path = Path(folder) / "job.json"
path.write_text(json.dumps(job), encoding="utf-8")
proc = subprocess.run( # noqa: S603 — 고정 실행 파일
["node", str(HERE / "helper_266_card_toggle.mjs"), str(bundle), str(path)],
cwd=ROOT,
capture_output=True,
text=True,
encoding="utf-8",
timeout=120,
)
assert proc.returncode == 0, proc.stderr
return json.loads(proc.stdout)
def _check_toggles(case: dict, returned) -> None:
steps = {step["tag"]: step for step in case["steps"]}
assert steps["open"]["cut"] > 0.5 # 깎기 측점
before = steps["open"]
assert all(step["calls"] == 1 for step in case["steps"]) # 걸음마다 다시 그림 한 번(441)
for tag in ("two_stage", "ditch_off", "soil", "slope"):
step = steps[tag]
assert case["target"] in step["updated"], tag # 카드 다시 그림 대상
assert step["line"] != before["line"], tag # 카드 설계선 바뀜
assert step["out"] == returned, tag
before = step
assert steps["two_stage"]["two_stage_slope"] is True
assert steps["ditch_off"]["ditch_enabled"] is False
assert steps["soil"]["ground_type"] == "soil"
assert steps["soil"]["cut"] != steps["open"]["cut"] # 면적 바뀜
def test_측점_같음_카드_단추마다_설계선_면적_바뀜_돌려줌(r):
_check_toggles(r["same"], 3)
def test_측점_어긋남_카드_자료는_고침_돌려줄_값만_null(r):
_check_toggles(r["mismatch"], None)
def test_서버_폴백에_카드_선택_실음(r):
key = f"{r['server']['at']:.3f}"
choice = (r["server"]["choices"] or {}).get(key)
assert choice is not None, r["server"]
assert choice["ground_type"] == "soil"
assert choice["two_stage_slope"] is True
assert choice["ditch_choice"] is False
def test_서버_카드_선택이_저장분_위에_얹힘():
stored = [{"chainage_m": 40.0, "design": {"ground_type": "ripping_rock", "berm": {"w": 1}}}]
rows = _with_design_choices(stored, {"40.000": {"ground_type": "soil"}, "60.123": {"x": 1}})
by_key = {round(float(r["chainage_m"]), 3): r["design"] for r in rows} # 서버 읽는 꼴
assert by_key[40.0] == {"ground_type": "soil", "berm": {"w": 1}}
assert by_key[60.123] == {"x": 1}
assert _with_design_choices(stored, None) == stored