⚠ **뿌리** — `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>
108 lines
5.7 KiB
Python
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
|