Files
Aislo/resources/tester/test_429_save_path.py
T
eomsangdonandClaude Opus 5.5 857b2d89d4 fix(445 B06 저장 patch): 바탕 = 캐시 설계 사용자 칸 + 세션 얹음 · 되돌림 null · 옮긴 측점 입력 · 집수정 범위 · 버린 수
- H3 — 횡단 patch 바탕을 캐시 설계 사용자 칸(측점마다 통째)으로 · 세션 고침은 벽 · 단 · 쪽 열쇠마다 합침(mergeCrossPatches)
- M2 — 벽 ↺ · 연동 풂 = 세션 null(안쪽 열쇠 지움) · 추가 벽 0 · 빈 전체 폭 0 을 세션에 남김 · 새 탭 − 가 저장분 단 수에서 출발
- H2 — 옮긴 관 · 구조물(정본 행 없음) = 지반 등 입력(CARRY_INPUT_KEYS)까지 실음 · 화면도 들고 감 · 서버 기본 설계가 그 입력으로 섬
- H5 — 노선 바꾼 [저장] 본문에 카드 선택 · 암선 · 암 경사 · 표준 횡단면 · 풍선 자리
- M1 — 집수정 내공 화면 범위 = 서버 1.0~2.0
- M5 — /save · /confirm 응답에 버린 patch 측점 · 기본 설계 실패 · 측점 맞춤 실패 · 화면 알림
- M7 — kindreset 을 측점 옮김 · 노선 저장에 따라 옮김
- 시험 test_445_save_patch · 기존 시험 꼴 맞춤(318 · 384 · 428 · 429 · fills_empty)

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

146 lines
6.4 KiB
Python

"""PLAN 429 — [저장] 길 정합(분석 424 9장 ④⑤⑧⑨).
- ④ 노선 [저장] 뒤 횡단 저장 = 고침이 없어도 한 번(서버가 지금 종단으로 횡단을 다시 셈 · 뒤에
나간 계획고 초안이 횡단 설계 높이에 실림) · 서버 [저장]이 횡단 설계 높이를 저장 종단에서 냄.
- ⑤ 상단측 차례(`sync_uphill_overrides_into_designs`)가 소단 · 깎기 경사 · 조절 값을 남김.
- ⑨ 추가 측점 = 계산 묶음 입력(stations 부터 다시) · 가게 초안 바뀜 · 되돌리기 → 묶음에 넣음.
[저장] 차례 실패 = 그 차례에서 멈춤(`test_design_store_save.py`).
Node 헬퍼 = `helper_429_save_path.cjs`.
"""
import asyncio
import json
import subprocess
from pathlib import Path
import pytest
from B06_Section.B06_Section_Engine_Design import compute_cross_design
from B06_Section.B06_Section_Router_Design import enforce_design_lines
from config.config_system import STANDARD_CROSS_SECTION
HERE = Path(__file__).parent
ROOT = HERE.parents[1]
@pytest.fixture(scope="module")
def out():
proc = subprocess.run(
["node", str(HERE / "helper_429_save_path.cjs")],
capture_output=True,
text=True,
encoding="utf-8",
timeout=60,
)
assert proc.returncode == 0, proc.stderr
return json.loads(proc.stdout)
def _ground() -> list[dict]:
"""왼쪽이 높은 비탈 — 왼쪽 깎기 · 오른쪽 쌓기(편절)."""
return [
{"offset_m": o / 2, "elevation_m": round(100.0 + 0.25 * o / 2, 4), "valid": True}
for o in range(-40, 41)
]
# ── ④ ────────────────────────────────────────────────────────────────────────
def test_노선_저장_뒤_고침이_없어도_횡단_저장_한_번(out):
# 본문 없이 = 서버가 다시 셈만 · 풍선 자리는 늘 실음(445 H5 — 횡단 보기 [저장]과 같은 본문)
assert out["noPatchSent"] == [["p1", 9, None, None, {"balloon_offsets": {}}]]
assert out["noPatchReturn"] == 0
def test_서버_저장이_횡단_높이를_저장_종단에서(tmp_path):
"""초안 계획고(101.2)가 종단 정본에 나간 뒤 [저장] 다시 셈 = 그 높이 · 옛 설계(100.0) 아님."""
old = compute_cross_design(
_ground(), 100.0, standard=STANDARD_CROSS_SECTION, ground_type="soil",
section_mode="left_cut", ditch_side="left",
) # fmt: skip
longitudinal = {
"design_profiles": [
{"samples": [{"chainage_m": 0.0, "elevation_m": 101.2},
{"chainage_m": 40.0, "elevation_m": 101.2}]}
]
} # fmt: skip
sections = [{"chainage_m": 20.0, "samples": _ground(), "design": old}]
assert enforce_design_lines(longitudinal, sections, tmp_path, STANDARD_CROSS_SECTION) == 1
assert sections[0]["design"]["design_elevation_m"] == pytest.approx(101.2, abs=1e-6)
# ── ⑤ ────────────────────────────────────────────────────────────────────────
def test_상단측_차례가_소단_깎기경사_조절값_남김(tmp_path, monkeypatch):
import B05_Profile.B05_Profile_Router_Confirm as confirm
import B06_Section.B06_Section_Repository as repo
import B06_Section.B06_Section_Repository_Bulk as bulk
import B06_Section.B06_Section_Router_Design as design_mod
user = {
"cut_slope_ratio_user": 0.7,
"berm": {"width_m": 1.0, "interval_m": 3.0, "slope_deg": 0.0, "depth_m": 1.0},
"berm_depth_left_m": 2.0,
"basin_adjust": {"w": 1.5},
"structure_note": "글",
"shoulder_extend_right_m": 0.3,
}
before = compute_cross_design(
_ground(), 100.0, standard=STANDARD_CROSS_SECTION, ground_type="soil",
section_mode="right_cut", ditch_side="right",
) # fmt: skip
stored = {**before, **user}
long_file = tmp_path / "long.json"
long_file.write_text("{}", encoding="utf-8")
async def designs(_connection, _route_id):
return [{"chainage_m": 20.0, "design": stored}]
async def longitudinal(*_args):
return {"data": {"options": {"standard_cross_section": STANDARD_CROSS_SECTION}}}
written: list = []
async def merge(_connection, *, route_id, entries, replace, project_id=None):
written.extend(entries)
monkeypatch.setattr(repo, "get_cross_section_designs", designs)
monkeypatch.setattr(confirm, "get_longitudinal_section", longitudinal)
monkeypatch.setattr(bulk, "merge_cross_section_designs", merge)
monkeypatch.setattr(design_mod, "resolve_longitudinal_path", lambda *_: long_file)
monkeypatch.setattr(
design_mod, "read_cross_design_inputs", lambda *_a: (_ground(), 100.0, False, {})
)
monkeypatch.setattr(design_mod, "ford_surface_drops", lambda _root: [])
asyncio.run(
confirm.sync_uphill_overrides_into_designs(
None, "p", 1, tmp_path, "long.json", [{"chainage_m": 20.0, "side": "left"}]
)
)
assert len(written) == 1
chainage, after = written[0]
assert chainage == 20.0 and before["ditch_type"] is None # 측구 끔 저장분도 셈
for key, value in user.items():
assert after[key] == value, key # 값 남음
assert after["cut_slope_ratio"] == pytest.approx(0.7) # 깎기 경사가 기하에 실림(키만 아님)
assert after["shoulder_extend_right_m"] == pytest.approx(0.3)
# ── ⑨ ────────────────────────────────────────────────────────────────────────
def test_추가_측점이_묶음_입력(out):
assert "input.plusStations" in out["stationsDeps"]
assert out["plusRan"][0] == "stations" and "route" not in out["plusRan"] # stations 부터
assert "cross" in out["plusRan"] # 뒤 단계까지 이어짐
assert out["plusSeen"] == [40]
assert out["sameValue"] is None and out["sameRan"] == [] # 같은 값은 안 셈
def test_가게_추가_측점_초안이_묶음에(out):
assert out["fed"] == [["plus", [12, 30]], ["plus", [12]]] # 초안 · 비우면 저장분
assert out["restoreFed"] == [["plus", [12, 55]]] # 되돌리기 = 입력과 다르면 넣음