"""366-3 측점 스냅 = 곡선 가운데(MC)를 일반측점에 — `logic/B05_Profile_RouteEdit_StationFit.ts` 를 Node(`helper_366_3_station_fit.cjs`)로 돌려 판정 + 편집기 이음(놓을 때 · applyEdit · 켤 때 · 되돌리기 · 추천 적용은 안 맞춤 · 상태줄 「측점 밖 곡선 N개」). ./venv/Scripts/python.exe -m pytest resources/tester/test_366_3_station_fit.py -q """ import json import subprocess from pathlib import Path import pytest ROOT = Path(__file__).resolve().parents[2] B05 = ROOT / "B05_Profile" HELPER = Path(__file__).with_name("helper_366_3_station_fit.cjs") TOL = 0.05 @pytest.fixture(scope="module") def got() -> dict: run = subprocess.run( # noqa: S603 — 고정 실행 파일 ["node", str(HELPER)], capture_output=True, text=True, encoding="utf-8", timeout=60 ) assert run.returncode == 0, run.stderr return json.loads(run.stdout) def test_맞추기_전_곡선은_측점_밖(got) -> None: assert got["before"]["off"] == got["before"]["curves"] == 5 def test_MC_는_일반측점_위(got) -> None: fit = got["all"] assert fit["curves"] == 5 and fit["off"] == 0 assert max(fit["residues"]) <= TOL stations = [round(mc / 20) for mc in fit["mcs"]] assert all(n >= 1 for n in stations) and len(set(stations)) == len(stations) def test_곡선_수는_측점_수_이하(got) -> None: assert got["all"]["curves"] <= got["all"]["innerStations"] def test_시점_종점_고정_IP_그대로_밀림은_간격_절반_안(got) -> None: for case in ("all", "locked", "fromLater"): moved = got[case]["moved"] assert moved[0] == 0 and moved[-1] == 0 assert max(moved) <= 10 + 1e-6 assert got["locked"]["moved"][3] == 0 def test_못_놓은_곡선은_그대로_개수(got) -> None: assert got["locked"]["off"] == 1 # 고정 IP 곡선 하나만 밖 · 나머지는 맞춤 assert got["everyLocked"]["off"] == 5 and max(got["everyLocked"]["moved"]) == 0 def test_고친_자리부터만(got) -> None: moved = got["fromLater"]["moved"] assert moved[1:4] == [0, 0, 0] and moved[4] > 0 assert got["fromLater"]["residues"][3:] == pytest.approx([0, 0], abs=TOL) def test_맞춘_선은_다시_맞춰도_그대로_간격_0_은_안_맞춤(got) -> None: assert max(got["again"]) == 0 and max(got["noInterval"]) == 0 @pytest.mark.parametrize("step", ["0.1", "1", "10"]) def test_스냅_간격_배수에_맞춤(got, step) -> None: """390-2 — MC 를 고른 간격 배수에 · 측점 밖 개수도 같은 간격 · 밀림은 간격 절반 안.""" size = float(step) case = got["steps"][step] assert case["offBefore"] == 5 and case["off"] == 0 and case["curves"] == 5 assert max(case["residues"]) <= min(TOL, size / 20) assert max(case["moved"]) <= size / 2 + 1e-6 assert case["moved"][0] == 0 and case["moved"][-1] == 0 def test_허용_오차는_간격_따라(got) -> None: assert got["tol"] == pytest.approx([0.005, 0.05, 0.05, 0.05]) def test_시작_자리는_고친_꺾임점_앞(got) -> None: start = got["start"] assert start["moved4"] == 3 and start["radius2"] == 1 assert start["dropLast"] == 4 # 지운 마지막 꺾임점 → 새 끝 앞 곡선부터 assert start["same"] == 1 and start["none"] == 1 def test_편집기_이음() -> None: edit = (B05 / "B05_Profile_UI_RouteEdit.ts").read_text("utf-8") assert "fitCurveStations(" in edit # 되돌리기 · 추천 [적용] · 초안 되살림이 지나는 restoreSnapshot 은 안 맞춤 at = edit.index("function restoreSnapshot(") assert "applyEdit(message, record, false);" in edit[at : at + 700] assert len(edit.splitlines()) <= 700 edits = (B05 / "logic" / "B05_Profile_UI_RouteEdit_Edits.ts").read_text("utf-8") assert "측점 밖 곡선 " in edits chrome = (B05 / "B05_Profile_UI_RouteEdit_Chrome.ts").read_text("utf-8") assert "곡선 가운데(MC)를 가까운 일반측점에" in chrome