Files
Aislo/resources/tester/test_b06_pipe_length.py
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

108 lines
5.7 KiB
Python

"""배수관 연장(m)이 **정본까지 간다** — 2026-09-08 B08 창 요청.
무슨 일이었나 — 관 길이는 횡단 기하가 **m 단위 올림까지** 끝낸 값인데(`CulvertLayout.pipe.lengthM`)
**정본(`design`)에 없었다.** 그래서 수량(B08)이 관 11개를 보고도 **연장을 못 내** 배수관 공종이
막혀 있었다. B08 창은 「계산을 서버로 옮기든가 저장 때 남기든가」 둘로 갈래를 냈는데, 실제로는
**그 계산이 이미 서버 Node 로 돌고 있었다**(`B06_Section_Server_Calc_Node` ←
`Server_Calc_Prebuild.recompute_server_side`, [저장]·[확정] 때). 그래서 **그 다리에 값 한 줄만**
더 실었다 — 계산을 두 벌로 짜지 않는다(CLAUDE.md 5장).
⚠ 이름은 `pipe_length_m` 로 **못 박았다**. 오늘 `back_len_cm` 을 엔진이 `stone_back_length_cm`
로 읽어 **저장값이 영영 안 닿던** 사고가 있었다 — 키 이름이 어긋나면 값은 조용히 사라진다.
"""
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_Schema import CrossSectionPatch # noqa: E402
_LAYOUTS = PROJECT_ROOT / "B06_Section" / "B06_Section_Structure_Layouts.ts"
_PERSIST = PROJECT_ROOT / "B06_Section" / "B06_Section_UI_Page_Persist.ts"
_TYPES = PROJECT_ROOT / "B06_Section" / "B06_Section_Api_Types.ts"
def test_패치로_받는다():
patch = CrossSectionPatch(chainage_m=85.05, pipe_length_m=7.0)
assert patch.pipe_length_m == 7.0
assert CrossSectionPatch(chainage_m=0).pipe_length_m is None
def test_음수는_막는다():
import pytest
with pytest.raises(ValueError):
CrossSectionPatch(chainage_m=0, pipe_length_m=-1)
def test_같은_길로_정본에_실린다():
"""구조물 면적이 실리는 그 목록에 함께 있어야 [저장]·[확정]·서버 초기값이 모두 나른다."""
source = _LAYOUTS.read_text(encoding="utf-8")
assert "export const STRUCTURE_ROW_KEYS" in source
block = source[source.index("STRUCTURE_ROW_KEYS = [") :][:300]
assert '"pipe_length_m"' in block, "관 길이가 정본에 얹는 키 목록에 없다"
assert '"cut_area_m2"' in block, "면적과 같은 목록이어야 한다(길이 하나만 따로 나르지 않는다)"
assert "STRUCTURE_ROW_KEYS" in _PERSIST.read_text(encoding="utf-8")
def test_면적을_못_내는_측점에서도_길이는_낸다():
"""설계선이 모자라 폐회로 면적을 못 내는 자리에도 **관은 서 있다** — 수량은 그 길이가 필요하다."""
source = _LAYOUTS.read_text(encoding="utf-8")
assert "return pipeRow;" in source, "면적 실패 시 null 로 떨어지면 관 길이까지 사라진다"
assert "layouts.culvert?.pipe?.lengthM" in source
def test_이름이_한_벌이다():
"""`back_len_cm` ↔ `stone_back_length_cm` 사고 재발 방지 — 이름을 적는 자리가 갈리면 안 된다.
저장 흐름(`_PERSIST`)은 이름을 **직접 안 적고** 공용 목록(`STRUCTURE_ROW_KEYS`)을 돈다 —
그것이 이름이 갈리지 않게 하는 방법이다. 그래서 여기서는 그 점을 지킨다.
"""
for path in (_LAYOUTS, _TYPES):
assert "pipe_length_m" in path.read_text(encoding="utf-8"), path.name
persist = _PERSIST.read_text(encoding="utf-8")
assert "pipe_length_m" not in persist, "이름을 또 적으면 갈릴 자리가 하나 더 생긴다"
assert "STRUCTURE_ROW_KEYS" in persist
def test_서버_화이트리스트가_TS_목록과_짝이다():
"""⚠ 서버에도 **받을 키 목록**이 따로 있다(`_AREA_KEYS`). 한쪽만 늘리면 Node 가 값을 내도
거기서 조용히 버려진다 — 관 길이를 더하며 실제로 걸린 자리다(2026-09-08).
"""
import re
from B06_Section.B06_Section_Server_Calc_Prebuild import _AREA_KEYS
block = _LAYOUTS.read_text(encoding="utf-8")
block = block[block.index("STRUCTURE_ROW_KEYS = [") :]
block = block[: block.index("] as const")]
ts_keys = set(re.findall(r'"([a-z_0-9]+)"', block))
assert ts_keys == set(_AREA_KEYS), (
f"짝이 갈림 — TS {sorted(ts_keys)} vs 서버 {sorted(_AREA_KEYS)}"
)
def test_소유_측점에만_싣는다():
"""⚠ 세트는 **폭의 절반까지 옆 측점에도** 붙는다(`attach_culvert_sets`). 그 자리에 길이를
실으면 **같은 관을 두 번** 센다 — 실측에서 관 9개에 값이 10곳 실렸다(2026-09-08).
가리는 열쇠는 스펙에 함께 얹는 `chainage_m`(그 시설이 **놓인** 자리)이다. 파이썬·TS
두 쪽이 같이 얹어야 한다 — 한쪽만 얹으면 세트 거울 시험이 깨진다.
⚠ **주인을 가리는 법이 2026-09-09 에 바뀌었다** — 거리(0.02m)로 자르던 것을
**가장 가까운 측점 하나**로 바꿨다. 관 자리와 측점 자리가 **스냅 때문에 최대 0.5m
어긋나기 때문**이다(관 440.241 → 측점 440.0). 거리로 자르면 그런 관은 주인이 없어
길이가 아무 데도 안 실리고 B08 이 「연장 없음」으로 막는다(실측: 배수관 넷).
**하나만 고르는 것**은 그대로라 두 번 세지 않는다.
"""
layouts = _LAYOUTS.read_text(encoding="utf-8")
assert "pipeOwnerChainage" in layouts, "소유 측점을 안 가리고 있다"
assert "section.culvert?.chainage_m" in layouts, "관이 놓인 자리를 안 읽고 있다"
py = (PROJECT_ROOT / "B06_Section" / "B06_Section_Engine_Culvert.py").read_text(encoding="utf-8")
ts = (PROJECT_ROOT / "common_util" / "common_util_culvert_sets.ts").read_text(encoding="utf-8")
assert '"chainage_m": pipe_chainage' in py
assert "chainage_m: pipeChainage" in ts