- 새 단추 꼴 danger-ghost(테두리 · 글 빨강 · 올리면 채움) · [초기화]만 - B05 [<] 흐림 · 주소 바뀔 때마다 [<] [>] 다시 맞춤 - 단계 판 B07~B09 · B06 [>] = 확정 뒤 그 주소 · 실패 · 자료 서기 전 = 머묾 - 노선 바뀐 채 확정 = 저장 뒤 물음 [머묾] / [B07 로](새 노선으로 확정 뒤 이동) - 막힘 = NOT_STARTED 만(STALE 은 색만) · B05·B06 화면 2 · 3 · 4 칸 늘 켬 · [저장] 뒤 단계 색 다시 읽음 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
105 lines
5.7 KiB
Python
105 lines
5.7 KiB
Python
"""[저장]이 여는 창구 넷이 **비어 있으면 안 나가는지**.
|
|
|
|
왜 (2026-09-07 조사) — [저장]은 창구 다섯을 순서대로 연다(관 옵션·상단측·관 목록·구조물·
|
|
`sections/save`). 넷은 초안이 비면 **요청 없이 즉시 돌아와야** 한다. 그래야 보통 저장이
|
|
1~2건으로 끝나고, 하나가 실패해도 나머지가 나간다(2026-08-29 사고 뒤 일부러 만든 구조).
|
|
|
|
이 성질이 깨지면 저장마다 빈 요청이 붙고, 「초안을 한 덩어리로 합치자」는 명분이 되살아난다.
|
|
합치지 않기로 한 근거(0-6)가 이 성질에 기대고 있으므로 그물을 남긴다.
|
|
|
|
코드를 읽어 지키는 시험이다 — 화면 왕복 없이 무너짐을 잡는 것이 목적이다.
|
|
"""
|
|
|
|
import re
|
|
from pathlib import Path
|
|
|
|
ROOT = Path(__file__).resolve().parents[2]
|
|
|
|
#: (파일, 함수 이름, 비었을 때 돌아가는 표시) — 472-6 한 본문 조각(빈 초안 = null 조각 = 서버가 그 차례를
|
|
#: 건너뜀 · 옛 창구 함수는 6-B 에서 걷음). 상단측은 463 부터 늘 나감(비정규 측점 횡단 맞춤 · 노선 바뀜이면 안 나감).
|
|
CHANNELS = [
|
|
(
|
|
ROOT / "B05_Profile" / "B05_Profile_Api_Pipes_Draft.ts",
|
|
"pipesPart",
|
|
"관 목록",
|
|
),
|
|
(
|
|
ROOT / "B05_Profile" / "B05_Profile_Api_Structures.ts",
|
|
"structuresPart",
|
|
"구조물",
|
|
),
|
|
(
|
|
ROOT / "B06_Section" / "B06_Section_Api_Culvert_Options.ts",
|
|
"culvertOptionsPart",
|
|
"관 옵션",
|
|
),
|
|
]
|
|
|
|
#: 비었을 때 빠져나가는 모양 — 빈 조각(`return null;`) 이른 반환.
|
|
_EARLY_RETURN = re.compile(r"\breturn\s+null\s*;")
|
|
|
|
|
|
def _body(path: Path, name: str) -> str:
|
|
"""함수 머리부터 다음 최상위 선언 전까지 — 이른 반환이 있는지만 보면 되므로 넉넉히 자른다."""
|
|
text = path.read_text(encoding="utf-8")
|
|
match = re.search(rf"(?:async\s+)?(?:export\s+)?(?:async\s+)?function\s+{name}\s*\(", text)
|
|
if match is None:
|
|
match = re.search(rf"\b{name}\s*\([^)]*\)\s*(?::[^{{]+)?\{{", text)
|
|
assert match is not None, f"{path.name} 에서 {name} 을 못 찾음"
|
|
return text[match.start() : match.start() + 900]
|
|
|
|
|
|
def test_창구_넷은_초안이_비면_요청을_안_낸다():
|
|
problems = []
|
|
for path, name, label in CHANNELS:
|
|
body = _body(path, name)
|
|
if not _EARLY_RETURN.search(body):
|
|
problems.append(f"{label}({path.name}:{name}) — 빈 초안에서 빠져나가는 자리가 없음")
|
|
assert not problems, "\n".join(problems)
|
|
|
|
|
|
def test_저장이_창구를_다섯_다_거친다():
|
|
"""순서·개수가 바뀌면 위 성질의 뜻도 바뀐다 — 자리를 못박는다.
|
|
|
|
차례는 저장 한 줄기(`A00_Common/design/design_save.ts`) 한 곳에 있다(계획서 24-2a) —
|
|
B05 [임시저장] · B06 [저장] · [확정]이 모두 그것을 탄다."""
|
|
save = (ROOT / "A00_Common" / "design" / "design_save.ts").read_text(encoding="utf-8")
|
|
for token in (
|
|
"culvertOptionsPart(",
|
|
"body: await routePartsBody(projectId),", # 상단측 차례 = 종단 보기 몫과 같은 본문(463)
|
|
"pipesPart(",
|
|
"structuresPart(",
|
|
):
|
|
assert token in save, f"{token} 가 저장 한 줄기에서 사라짐"
|
|
# 472-6 — 노선이 바뀌든 안 바뀌든 한 본문 조각(조정창 구간값 = 세션 `culvertopt` · 옛 창구 차례 걷음)
|
|
assert 'culvertOptionsPart(stateKey("culvertopt", projectId, routeId))' in save, (
|
|
"B06 조정창 구간값이 한 본문 저장에서 빠짐"
|
|
)
|
|
# 차례가 실패하면 서버가 차례 이름째 되돌림(429 ⑨ · 전부 아니면 없음)
|
|
assert "STEP_LABEL[error.step]" in save, "한 본문 실패 차례 이름이 빠짐"
|
|
api = (ROOT / "A00_Common" / "design" / "design_save_api.ts").read_text(encoding="utf-8")
|
|
order = api[api.index("SAVE_PART_KEYS = [") :]
|
|
order = order[: order.index("]")]
|
|
names = ["pipes", "structures", "alignment", "culvertOptions", "uphill", "cross"]
|
|
assert [order.index(f'"{n}"') for n in names] == sorted(order.index(f'"{n}"') for n in names), (
|
|
"저장 차례가 바뀜 — 관 → 구조물 → 계획고 → 암거 옵션 → 오르막 → 횡단"
|
|
)
|
|
|
|
|
|
def test_두_페이지가_저장_한_줄기를_탄다():
|
|
"""[저장] · [확정]은 한 페이지 한 벌이 두 보기 몫을 모아 저장 한 줄기 한 번으로 보낸다
|
|
(계획서 24-2a · 24-3b) — 보기 몫은 창구를 따로 부르지 않는다."""
|
|
shared = (ROOT / "A00_Common" / "design" / "design_shared.ts").read_text(encoding="utf-8")
|
|
actions = (ROOT / "B05_Profile" / "B05_Profile_UI_Page_Actions.ts").read_text(encoding="utf-8")
|
|
persist = (ROOT / "B06_Section" / "B06_Section_UI_Page_Persist.ts").read_text(encoding="utf-8")
|
|
assert shared.count("await saveDesign(") == 1, "저장 한 줄기를 한 자리에서만 부름"
|
|
page = (ROOT / "A00_Common" / "design" / "design_page.ts").read_text(encoding="utf-8")
|
|
assert "saveSharedDesign(shared, true, route)" in shared, "확정(B06 [>])이 한 줄기를 안 탐"
|
|
assert "confirmAndGo(shared, route)" in page, "B06 [>] · 단계 판 B07~B09 가 확정을 안 탐(496-1)"
|
|
assert 'await shared.ensureView("cross")' in shared, "종단 보기 [확정]이 횡단 자료를 못 얻음"
|
|
for name, text in (("종단 보기", actions), ("횡단 보기", persist), ("한 벌", shared)):
|
|
for token in ("flushPendingPipes(", "flushPendingStructures(", "flushUphillOverrides("):
|
|
assert token not in text, f"{name} 가 {token} 를 따로 부름 — 차례가 갈림"
|
|
assert "export function profileSaveParts(" in actions
|
|
assert "export function crossSaveParts(" in persist
|