⚠ **뿌리** — `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>
116 lines
3.8 KiB
Python
116 lines
3.8 KiB
Python
"""구간형 기준점·옵션 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"}
|
|
)
|