"""구간형 기준점·옵션 phase — 스키마 확장 (PLAN 2026-08-17 컨테이너 병합 1단계). 사용자 확정: 점형 = 기준 측점이 마킹 위치. 구간형 = **기준점에 마킹** + 시작·종료 측점. 기존 스키마는 구간형에 chainage_m을 금지하고 start_m에 마킹했다 — 기준점을 허용하고 시작≤기준≤종료를 검증한다. 기존 저장분(기준점 없음)은 start_m으로 채운다(하위 호환). """ import pytest from pydantic import ValidationError from B05_Profile.B05_Profile_Structures_Schema import ( StructureInstance, StructureOptionField, ) def _interval(**overrides): data = { "type_id": "ditch_side", "placement": "interval", "start_m": 100.0, "end_m": 140.0, "options": {}, } data.update(overrides) return StructureInstance.model_validate(data) # ── 구간형 기준점 ─────────────────────────────────────────────────────────── def test_interval_accepts_anchor_within_span(): item = _interval(chainage_m=115.0) assert item.chainage_m == 115.0 assert item.anchor_m() == 115.0 def test_interval_without_anchor_defaults_to_start(): """기존 저장분(기준점 없음) — start_m이 기준점이 된다 (하위 호환).""" item = _interval() assert item.chainage_m == 100.0 assert item.anchor_m() == 100.0 def test_interval_anchor_outside_span_is_rejected(): with pytest.raises(ValidationError, match="기준점"): _interval(chainage_m=90.0) with pytest.raises(ValidationError, match="기준점"): _interval(chainage_m=141.0) def test_interval_anchor_at_boundaries_is_accepted(): assert _interval(chainage_m=100.0).anchor_m() == 100.0 assert _interval(chainage_m=140.0).anchor_m() == 140.0 def test_interval_still_requires_ordered_span(): with pytest.raises(ValidationError): _interval(start_m=140.0, end_m=100.0) with pytest.raises(ValidationError): _interval(start_m=100.0, end_m=None) # ── 점형·부지형은 기존 규칙 유지 ──────────────────────────────────────────── def test_point_still_rejects_span_fields(): with pytest.raises(ValidationError): StructureInstance.model_validate( { "type_id": "erosion_check", "placement": "point", "chainage_m": 50.0, "start_m": 40.0, "end_m": 60.0, "options": {}, } ) def test_point_anchor_is_chainage(): item = StructureInstance.model_validate( {"type_id": "erosion_check", "placement": "point", "chainage_m": 50.0, "options": {}} ) assert item.anchor_m() == 50.0 # ── 옵션 phase — B05(배치) / detail(B06·B07 상세) ────────────────────────── def test_option_phase_defaults_to_b05(): field = StructureOptionField.model_validate( {"key": "form", "label": "형식", "input": "select", "choices": ["A"]} ) assert field.phase == "b05" def test_option_phase_detail_roundtrip(): field = StructureOptionField.model_validate( { "key": "height_m", "label": "높이", "input": "number", "required": True, "phase": "detail", } ) assert field.phase == "detail" and field.required is True def test_option_phase_rejects_unknown_value(): with pytest.raises(ValidationError): StructureOptionField.model_validate( {"key": "x", "label": "x", "input": "text", "phase": "b07"} )