"""B06 배수관 세트(배관·기슭막이·보호공) 엔진 검증. 정본(`pipe_points.json`) + 레지스트리 기본값 조합이 계획서(3-0 근거 표)대로 나오는지, 배관 외 시설(BOX암거 등)이 이번 범위에서 빠지는지 확인한다. """ import json import sys from pathlib import Path PROJECT_ROOT = Path(__file__).resolve().parents[2] if str(PROJECT_ROOT) not in sys.path: sys.path.insert(0, str(PROJECT_ROOT)) from B06_Section.B06_Section_Engine_Culvert import ( # noqa: E402 APRON_LENGTH_FACTOR, APRON_THICKNESS_M, MIN_PIPE_COVER_M, REVET_FACE_SLOPE, _culvert_set, attach_culvert_sets, pipe_points_file, ) def test_cross_reference_constants_match_plan(): """계획서 3-0 표 값 — 토피 0.5m·보호공 길이 2배·두께 0.45m(사용자 확정)·전면 1:0.3.""" assert MIN_PIPE_COVER_M == 0.5 assert APRON_LENGTH_FACTOR == 2.0 assert APRON_THICKNESS_M == 0.45 assert REVET_FACE_SLOPE == 0.3 # 배관은 수평도 가능 — 세트에 경사 강제값을 싣지 않는다(2026-08-20 사용자 확정). assert "min_slope" not in _culvert_set(None) def test_empty_options_fall_back_to_registry_defaults(): """옵션이 빈 저장분은 레지스트리 기본값(Ø1000·기슭막이 H2.5/L10)으로 채운다.""" spec = _culvert_set(None) assert spec["type"] == "pipe" assert spec["diameter_m"] == 1.0 assert spec["min_cover_m"] == MIN_PIPE_COVER_M for side in ("inlet", "outlet"): part = spec[side] assert part["structure"] == "기슭막이" assert part["revet_height_m"] == 2.5 assert part["revet_length_m"] == 10 assert part["face_slope"] == REVET_FACE_SLOPE # 보호공 = 낙차고(2.5) × 2 = 5.0m, 두께 0.45m(사용자 확정). assert part["apron_length_m"] == 5.0 assert part["apron_thickness_m"] == 0.45 # 레지스트리 기본 형태: 유입 찰 / 유출 메. assert spec["inlet"]["revet_form"] == "돌쌓기(찰)" assert spec["outlet"]["revet_form"] == "돌쌓기(메)" def test_basin_inlet_skips_revet_and_apron(): """유입구 = 집수정이면 그쪽 기슭막이·보호공을 만들지 않는다(라벨만).""" spec = _culvert_set({"inlet_type": "집수정"}) assert spec["inlet"]["structure"] == "집수정" assert "revet_height_m" not in spec["inlet"] assert "apron_length_m" not in spec["inlet"] # 유출측은 그대로 세트를 갖춘다. assert spec["outlet"]["structure"] == "기슭막이" assert spec["outlet"]["apron_length_m"] == 5.0 def test_string_diameter_and_custom_height(): """관경 문자열 저장분("800")과 사용자 높이 변경이 그대로 반영된다.""" spec = _culvert_set({"pipe_diameter_mm": "800", "outlet_revet_height_m": 1.5}) assert spec["diameter_m"] == 0.8 assert spec["outlet"]["revet_height_m"] == 1.5 assert spec["outlet"]["apron_length_m"] == 3.0 # 1.5 × 2 def test_attach_only_pipe_facilities(tmp_path): """배관은 `culvert`, 세월교는 `ford`, BOX암거는 `box` 키로 얹는다. 배관 매칭은 chainage ±0.02m 그대로다. 세월교·BOX암거는 구체 폭만큼 이어져 기준 측점 전후 절반까지 붙는다(2026-08-25 사용자 확정) — 아래 100.0m은 어느 쪽 범위에도 들지 않는다. """ target = pipe_points_file(tmp_path) target.parent.mkdir(parents=True) target.write_text( json.dumps( { "route_signature": "x", "points": [ { "chainage_m": 84.3, "source": "spacing", "options": {"pipe_diameter_mm": 1000}, }, {"chainage_m": 149.73, "source": "stream", "facility": "ford_bridge"}, {"chainage_m": 200.92, "source": "spacing", "facility": "box_culvert"}, ], }, ensure_ascii=False, ), encoding="utf-8", ) cross_sections = [ {"chainage_m": 84.3, "samples": []}, {"chainage_m": 149.73, "samples": []}, {"chainage_m": 200.92, "samples": []}, {"chainage_m": 100.0, "samples": []}, ] attached = attach_culvert_sets(tmp_path, cross_sections) assert attached == 3 assert "culvert" in cross_sections[0] assert cross_sections[0]["culvert"]["diameter_m"] == 1.0 # 세월교·BOX암거는 그림이 달라 키를 나눈다 — 배수관 소비처가 집어가면 안 된다. assert cross_sections[1]["ford"]["type"] == "ford" assert cross_sections[2]["box"]["type"] == "box" assert all("culvert" not in section for section in cross_sections[1:]) assert all("ford" not in section for section in (cross_sections[0], *cross_sections[2:])) def test_attach_without_file_is_noop(tmp_path): """정본 파일이 없으면 아무 것도 얹지 않고 0을 돌려준다.""" sections = [{"chainage_m": 10.0}] assert attach_culvert_sets(tmp_path, sections) == 0 assert "culvert" not in sections[0]