Files
Aislo/resources/tester/test_b04_pipe_points_facility.py
T
eomsangdonandClaude Opus 5 0ef32b5279 chore(tester): 시험을 resources/tester/ 로 옮김 — 창끼리 건너가게
⚠ **뿌리** — `tmp/` 는 창 사이에 안 건너감(실측 확인: 상대 창이 놓은 `tmp/_sync_probe.txt`
가 시간을 두고 두 번 봐도 안 보임). 그래서 **정본(등록부 스키마)만 건너가고 그것을 읽는
시험은 안 건너가** 오늘 두 번, 같은 시험이 **연 창은 통과·받은 창은 실패**가 됐음.

- `tmp/tests/*` 를 `resources/tester/` 로 **복사**(127 파일). 내용은 **한 줄도 안 고침**
- `tmp/tests` 는 **남겨 둠** — 되돌릴 자리(사용자 지시)
- 실행: `./venv/Scripts/python.exe -m pytest resources/tester/ -q`
  옮기기 전과 **같은 수**: 617 통과 / 22 건너뜀 / 실패 0

⇒ 이제 시험·예외·까닭이 **정본과 함께** 움직임. 오늘 세운 「예외는 정본 스키마에」와 짝임.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-09 17:12:30 +09:00

201 lines
7.6 KiB
Python

"""계곡 통과 시설 확장 — PipePoint의 시설종류·구간·옵션 (PLAN 2026-08-17 컨테이너 병합 1단계).
배관 정본 `pipe_points.json`은 유역 계산의 입력이라 저장소를 옮기지 않고 제자리 확장한다.
구 파일(`{chainage_m, source}`뿐)은 배관·폭 미지정으로 읽혀야 한다 (하위 호환).
"""
import pytest
from common_util.common_util_drainage_pipes import (
PIPE_FACILITY_BOX,
PIPE_FACILITY_FORD_BRIDGE,
PIPE_FACILITY_FORD_PAVEMENT,
PIPE_FACILITY_PIPE,
PIPE_SOURCE_STREAM,
PIPE_SOURCE_USER,
PipePoint,
carry_facility_attributes,
parse_pipe_points,
)
# ── 하위 호환 — 구 파일 형식 ────────────────────────────────────────────────
def test_legacy_dict_defaults_to_pipe_with_no_span():
points = parse_pipe_points([{"chainage_m": 120.0, "source": "stream"}])
assert len(points) == 1
point = points[0]
assert point.facility == PIPE_FACILITY_PIPE
assert point.start_m is None and point.end_m is None
assert point.source == PIPE_SOURCE_STREAM
def test_legacy_bare_number_still_parses():
points = parse_pipe_points([80.5])
assert points[0].chainage_m == 80.5
assert points[0].facility == PIPE_FACILITY_PIPE
def test_legacy_dict_roundtrip_stays_compact():
"""구 형식 저장분은 확장 필드 없이 그대로 다시 저장된다 (파일 불변)."""
point = parse_pipe_points([{"chainage_m": 60.0, "source": "user"}])[0]
assert point.as_dict() == {"chainage_m": 60.0, "source": PIPE_SOURCE_USER}
# ── 시설 종류 ───────────────────────────────────────────────────────────────
def test_facility_roundtrip():
for facility in (
PIPE_FACILITY_PIPE,
PIPE_FACILITY_BOX,
PIPE_FACILITY_FORD_PAVEMENT,
PIPE_FACILITY_FORD_BRIDGE,
):
data = {"chainage_m": 100.0, "facility": facility}
parsed = parse_pipe_points([data])[0]
assert parsed.facility == facility
assert parse_pipe_points([parsed.as_dict()])[0].facility == facility
def test_unknown_facility_falls_back_to_pipe():
points = parse_pipe_points([{"chainage_m": 100.0, "facility": "bridge"}])
assert points[0].facility == PIPE_FACILITY_PIPE
# ── 구간 (기준점 + 시작·종료) ──────────────────────────────────────────────
def test_span_roundtrip():
data = {"chainage_m": 100.0, "facility": "box_culvert", "start_m": 92.0, "end_m": 111.0}
point = parse_pipe_points([data])[0]
assert (point.start_m, point.end_m) == (92.0, 111.0)
again = parse_pipe_points([point.as_dict()])[0]
assert (again.start_m, again.end_m) == (92.0, 111.0)
def test_span_is_normalized_to_contain_anchor():
"""시작·종료가 뒤집혔거나 기준점을 안 품으면 정규화한다 (파서는 관대하게)."""
flipped = parse_pipe_points([{"chainage_m": 100.0, "start_m": 110.0, "end_m": 90.0}])[0]
assert (flipped.start_m, flipped.end_m) == (90.0, 110.0)
outside = parse_pipe_points([{"chainage_m": 100.0, "start_m": 104.0, "end_m": 112.0}])[0]
assert outside.start_m == 100.0 and outside.end_m == 112.0
def test_half_span_is_completed_with_anchor():
"""한쪽만 오면 기준점으로 나머지를 채운다."""
point = parse_pipe_points([{"chainage_m": 100.0, "end_m": 115.0}])[0]
assert (point.start_m, point.end_m) == (100.0, 115.0)
def test_non_numeric_span_is_dropped():
point = parse_pipe_points([{"chainage_m": 100.0, "start_m": "십", "end_m": None}])[0]
assert point.start_m is None and point.end_m is None
# ── 시설 옵션 (세월교 관 종류/크기/수량 등) ────────────────────────────────
def test_options_roundtrip():
data = {
"chainage_m": 100.0,
"facility": "ford_bridge",
"options": {"pipe_kind": "흄관", "pipe_diameter_mm": 800, "pipe_count": 3},
}
point = parse_pipe_points([data])[0]
assert point.options == {"pipe_kind": "흄관", "pipe_diameter_mm": 800, "pipe_count": 3}
again = parse_pipe_points([point.as_dict()])[0]
assert again.options == point.options
def test_pipe_accessory_options_roundtrip():
"""배관 부속 키(2026-08-17 재편) — 유형·집수정·기슭막이·돌붙임이 왕복 보존되는지."""
data = {
"chainage_m": 240.0,
"facility": "pipe",
"start_m": 236.0,
"end_m": 246.0,
"options": {
"flow_type": "계곡부형",
"pipe_diameter_mm": 1000,
"catch_basin": "없음",
"revetment": "있음",
"revet_length_m": 10.0,
"revet_front_m": 4.0,
"stone_pitching": "있음",
},
}
point = parse_pipe_points([data])[0]
again = parse_pipe_points([point.as_dict()])[0]
assert again.options == data["options"]
assert (again.start_m, again.end_m) == (236.0, 246.0)
def test_non_dict_options_are_dropped():
point = parse_pipe_points([{"chainage_m": 100.0, "options": "D800x3"}])[0]
assert point.options is None
def test_empty_options_are_omitted_from_dict():
point = PipePoint(chainage_m=50.0, options={})
assert "options" not in point.as_dict()
# ── 시설 속성 승계 — 세부유역 계산 왕복에서 종류가 사라지면 안 된다 ────────
def test_carry_attributes_restores_facility_after_rebuild():
"""계산기는 chainage만 다뤄 재구성 목록이 전부 기본 배관이 된다 — 되붙여야 한다."""
requested = parse_pipe_points(
[
{
"chainage_m": 100.0,
"facility": "ford_bridge",
"start_m": 95.0,
"end_m": 108.0,
"options": {"pipe_count": 2},
},
{"chainage_m": 200.0},
]
)
rebuilt = [PipePoint(chainage_m=100.0, source="user"), PipePoint(chainage_m=200.0)]
carried = carry_facility_attributes(rebuilt, requested)
assert carried[0].facility == PIPE_FACILITY_FORD_BRIDGE
assert (carried[0].start_m, carried[0].end_m) == (95.0, 108.0)
assert carried[0].options == {"pipe_count": 2}
assert carried[1].facility == PIPE_FACILITY_PIPE
def test_carry_attributes_uses_nearest_when_chainage_shifted():
"""계산기가 좌표를 미세 조정해도(스냅) 가장 가까운 원본에서 승계한다 — _retag과 동일 기준."""
requested = parse_pipe_points([{"chainage_m": 150.0, "facility": "box_culvert"}])
rebuilt = [PipePoint(chainage_m=150.37)]
carried = carry_facility_attributes(rebuilt, requested)
assert carried[0].facility == PIPE_FACILITY_BOX
def test_carry_attributes_without_reference_is_noop():
rebuilt = [PipePoint(chainage_m=100.0)]
assert carry_facility_attributes(rebuilt, []) is rebuilt
assert rebuilt[0].facility == PIPE_FACILITY_PIPE
# ── 정렬 유지 ───────────────────────────────────────────────────────────────
def test_points_stay_sorted_by_chainage():
points = parse_pipe_points(
[
{"chainage_m": 300.0, "facility": "ford_pavement"},
{"chainage_m": 100.0},
{"chainage_m": 200.0, "facility": "box_culvert"},
]
)
assert [point.chainage_m for point in points] == [100.0, 200.0, 300.0]