Files
Aislo/resources/tester/test_design_store_save.py
T
eomsangdonandClaude Opus 5.5 4b6800e387 feat(472-6 6-B 노선 바뀐 [저장] 한 본문): 노선 저장 · 관 · 구조물 다시 얹기 · 횡단 고침 넘김을 서버 한 창구로 — 전부 아니면 없음
- POST /design/save 에 route(노선 초안) · carry(옛 노선 세션 칸) 칸 · 서버 = 노선 저장 → Node facility.rebase(rebasePipeDraft · rebaseStructureDraft) → 6-A 차례(새 노선) → Node stations.carry(routeCarryPatches) → 횡단 저장
- 되돌림 범위 = 노선 행 · 자식 표 · 단계 · 배수 폴더 · 노선 파일 · 지표면 확정(설계 지형 바꿈)
- 손 상단측은 안 넘김(요구 274) · 노선 바뀐 [확정] = [저장](새 노선 본 뒤 다시)
- 브라우저 = 노선 초안 + 사용자 칸만 한 본문 · 옛 차례 길(saveRouteDraft 뒤 flush · carryAfterRouteSave · keepDraftsOnNewRoute) · 부름 0 된 flush 창구 여섯 · saveCachedCrossPatches 걷음
- 등록부 R9 stations.carry · F10 facility.rebase · 옛 차례 단언 시험 한 본문 꼴로 · test_472_6_route_save(옛 길 = 새 길 · 실패 되돌림)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 12:07:12 +09:00

173 lines
7.1 KiB
Python

# -*- coding: utf-8 -*-
"""설계 가게 한 곳 · 저장 한 줄기(계획서 24-2a) 회귀검증.
- 가게(`design_store`) — 밑은 등록 키 · 같은 칸이면 어느 가게든 같은 값 · 듣는 쪽.
- 계획고 가게 한 벌 — B05 종단 패널 · B06 계획선 편집이 같은 벌을 씀(옛 두 가게 덮어쓰기 흠).
- 저장 한 줄기(`saveDesign`) — 관 → 구조물 → 계획고 → 암거 옵션 → 오르막 → 횡단 · 어느 차례든
실패하면 거기서 멈추고 차례 이름째 알림(429 ⑨).
Node 헬퍼(`helper_design_store_save.cjs`)가 TS 를 트랜스파일해 돌린다.
"""
import json
import os
import re
import subprocess
import pytest
HERE = os.path.dirname(os.path.abspath(__file__))
@pytest.fixture(scope="module")
def out():
proc = subprocess.run(
["node", os.path.join(HERE, "helper_design_store_save.cjs")],
capture_output=True,
text=True,
encoding="utf-8",
timeout=60,
)
assert proc.returncode == 0, proc.stderr
return json.loads(proc.stdout)
def test_가게는_등록_키_밑_같은_칸은_같은_값(out):
assert out["pipesShared"] == [{"chainage_m": 10, "source": "user"}]
assert out["pipesKey"] == ["aislo:draft:pipes:p1"]
assert out["heard"] == [1], "끊은 뒤에는 안 불려야 함"
assert out["otherProject"] is None
assert out["noRoute"] is None
assert out["selection"] == {"chainageM": 12.5, "structureId": "s-1"}
def test_계획고_가게_한_벌(out):
assert out["b05SeesB06"] == {"20.000": 0.5}
assert out["b05Dirty"] is True
assert out["afterBoth"] == {"20.000": 0.5, "60.000": -0.2}
assert out["draft"] == out["afterBoth"] == out["pending"]
assert out["rebuiltSeenByB06"] == {"20.000": 0.1}
def test_저장_차례(out):
# 472-6 — 차례 = 한 본문 칸(브라우저) · 서버 한 창구 · 노선(346-3 · 6-B)은 서버 맨 앞
api = open(
os.path.join(HERE, "..", "..", "A00_Common", "design", "design_save_api.ts"),
encoding="utf-8",
).read()
keys = api[api.index("SAVE_PART_KEYS = [") :]
keys = re.findall(r'"([A-Za-z]+)"', keys[: keys.index("]")])
assert keys == [
"settings", # 448 페이지 설정 — 노선 뒤
"pipes",
"structures",
"plusStations", # 368-2 추가 측점 — 계획고 앞
"alignment",
"culvertOptions",
"uphill",
"cross",
]
server = open(
os.path.join(HERE, "..", "..", "B06_Section", "B06_Section_Router_DesignSave.py"),
encoding="utf-8",
).read()
at = [re.search(rf'step\(\s*"{key}"', server).start() for key in ["route", *keys]]
assert at == sorted(at)
assert out["saveOk"] is True
# 472-6 — 노선 초안 없으면 조각을 모아 한 요청(서버 한 창구 · 전부 아니면 없음) ·
# 저장 뒤 조각 정리는 차례대로 · 본문 없는 조각(추가 측점 초안 없음)도 정리는 돎
assert out["saveCalls"] == [
"overlay:on",
"prepare",
"post:7:false:pipes,structures,alignment,culvertOptions,uphill,cross",
"pipes:after",
"pipes:screen",
"structures:after",
"plusStations:after",
"culvert:after:aislo:draft:culvertopt:p1:7",
"cross:after:false",
"done:false",
"overlay:off",
]
assert out["afterSave"] == {"dirty": False, "draft": None}
def test_구조물_실패는_멈추고_알림_노선없음(out):
"""429 ⑨ · 472-6 — 서버가 구조물 차례에서 실패 · 되돌림 → 정리 · done 없음 · 차례 이름째."""
assert out["tolerant"] == [
"overlay:on",
"post:7:true:pipes,structures,culvertOptions,uphill,cross",
"failed",
"overlay:off",
]
assert out["tolerantOk"] is False
assert out["tolerantError"].startswith("구조물 차례에서 실패 — 아무것도 저장하지 않았습니다")
assert "structures 실패" in out["tolerantError"] # 까닭 그대로
assert out["noRouteSave"] is False and out["noRouteCalls"] == []
def test_초기화는_초안과_한_벌을_버림(out):
assert out["cleared"] == {"draft": None, "pending": None}
def test_노선이_바뀌면_노선_먼저_뒤_차례는_새_노선(out):
"""346-3 · 472-6 6-B — 노선 초안이 있으면 같은 한 본문에 `route` · `carry`(옛 노선 7 세션 칸) · 관 · 구조물
기본 조각 · 계획고 초안(옛 노선 7 칸 · 서버가 새 노선 9 로) · 상단측 안 보냄(요구 274) · 화면 몫 일(옛 노선 판)은
건너뜀 · 횡단 = 넘김 조각 · 다 저장한 뒤 정리 · 결과 한 줄 · 다시 세움."""
assert out["routeOk"] is True
assert out["routeCalls"] == [
"overlay:on",
"prepare",
"post:7:false:pipes,structures,alignment,culvertOptions,cross:route(carry 7)",
'alignment:7:{"30.000":0.2}',
"routeSaved",
"pipes:after",
"structures:after",
"plusStations:after",
"culvert:after:aislo:draft:culvertopt:p1:7",
"cross:carried:after",
"summary:9:2",
"toast:info",
"done:false",
"rebuild",
"overlay:off",
]
assert "pipes:screen" not in out["routeCalls"] and "alignment:screen" not in out["routeCalls"]
assert "cross:false" not in out["routeCalls"] # 옛 노선 횡단 본문 대신 넘김
def test_노선이_그대로면_안_보냄(out):
assert not any("route" in call for call in out["saveCalls"])
def test_노선_저장_실패는_멈추고_초안_남음(out):
assert out["routeFailOk"] is False
assert out["routeFailCalls"] == [
"overlay:on",
"post:7:false:pipes,structures,alignment,culvertOptions,cross:route(carry 7)",
'alignment:7:{"50.000":0.3}',
"failed", # 서버가 노선 차례에서 되돌림 · 정리 없음
"overlay:off",
]
assert out["routeFailDraft"] == {"50.000": 0.3} # 계획고 초안 그대로
def test_확정_중_노선이_바뀌면_다시_세우고_확정은_다시(out):
calls = out["routeConfirmCalls"]
assert calls[1].startswith("post:7:true:") and calls[1].endswith(":route(carry 7)")
assert calls[-2:] == ["rebuild", "overlay:off"]
assert "done:true" not in calls and "cross:true" not in calls
def test_노선_초안_칸_계획고가_새_노선으로(out):
"""346-3 덧 — 노선 초안 동안 계획고는 노선 0 칸(sub7) · 노선 저장 뒤 그 칸이 새 노선 9 로 · 0 · 7 칸 다 비움."""
assert out["draftRouteCalls"] == ['alignment:7:{"35.000":0.4}'] # 서버가 새 노선 9 로
assert out["draftRouteLeft"] == {"draft": None, "old": None}
design = os.path.join(HERE, "..", "..", "A00_Common", "design")
with open(os.path.join(design, "design_pipeline_page.ts"), encoding="utf-8") as f:
pipeline = f.read()
with open(os.path.join(design, "design_save.ts"), encoding="utf-8") as f:
save = f.read()
assert "const DRAFT_ROUTE_ID = 0;" in save
if "export const DRAFT_ROUTE_ID" in pipeline: # 합친 뒤 — 두 값이 같아야 함
assert "export const DRAFT_ROUTE_ID = 0;" in pipeline