"""요구 298-3 · 302 D1 — B05 · B06 [저장] 모양 · 저장 알림 정리(PLAN 498-3). ① 노선 모양 안 바뀐 [저장] = 잠금 없이 뒤에서 · 끝나면 알림(`design_shared` done) ② 노선 모양 바뀐 [저장] = 잠금 + 다시 세움 · 옛 페이지 헛 다시 읽기 없음(`done(false, false)`) ③ 고친 것 없고 단계 2 · 3 이미 완료 = [>] 저장 없이 바로 이동 · 그 밖 = 496 확정 그대로 ④ 저장 중 확정 = 잠그고 앞 저장 끝을 기다린 뒤 지금 화면으로 · ⑤ 저장 중 [저장] = 막음 + 알림 알림 — 결과 한 줄(관 지킴 · 노선 밖 고침 · 배수 낡음 · 구조물 옮김) · 노선 밖 되풀이 지움 · 남김 = 새 교차 관 한 줄 · 따라감 실패. """ import json import pathlib import subprocess import pytest HERE = pathlib.Path(__file__).resolve().parent ROOT = HERE.parents[1] BUSY = "toast:warning:이전 수정을 저장 중입니다. 잠시 후 다시 해 주세요." def _read(rel: str) -> str: return (ROOT / rel).read_text(encoding="utf-8") @pytest.fixture(scope="module") def out() -> dict: proc = subprocess.run( ["node", str(HERE / "helper_298_3_save_shape.cjs")], capture_output=True, text=True, encoding="utf-8", timeout=60, ) assert proc.returncode == 0, proc.stderr return json.loads(proc.stdout) def test_1_노선_그대로_저장은_잠금_없음(out): assert out["plain"]["ok"] is True assert out["plain"]["calls"] == [ "post:false", "pipes:after", "cross:after", "clean", "done:false:undefined", ] assert not any(c.startswith("overlay") for c in out["plain"]["calls"]) def test_2_노선_모양_바뀐_저장은_잠금_다시_세움(out): calls = out["route"]["calls"] assert calls[0] == "overlay:on" and calls[-1] == "overlay:off" assert calls.index("done:false:false") < calls.index( "rebuild" ) # 다시 읽기 없이 알림만 · 다시 세움 assert calls.index("clean") < calls.index("done:false:false") def test_2_노선_저장_알림은_따라감_실패_새_교차_관만(out): toasts = [c for c in out["route"]["calls"] if c.startswith("toast:")] assert toasts == [ "toast:error:구조물 따라감 실패 — 옛 누가거리 그대로입니다.", "toast:info:새 교차 관 2개 — 새 세류선 교차점에 더했습니다.", ] assert not [c for c in out["routeQuiet"]["calls"] if c.startswith("toast:")] def test_저장_도중_고침은_정리_깨끗한_표_건너뜀(out): assert out["touched"]["ok"] is True assert out["touched"]["calls"] == ["post:false", "done:false:undefined"] def test_5_저장_중_저장은_막음_알림(out): assert out["busyWhile"] is True assert out["second"] is False assert "job2" not in out["queueCalls"] assert out["queueCalls"].count(BUSY) == 2 # 저장 중 [저장] · 확정 중 확정 def test_4_저장_중_확정은_앞_저장_끝을_기다림(out): calls = out["queueCalls"] assert out["firstResult"] == "one" and out["confirmResult"] == "three" assert calls.index("overlay:on") < calls.index("release") < calls.index("job1:end") assert calls.index("job1:end") < calls.index("job3") < calls.index("overlay:off") assert out["thirdBlocked"] is False and "job4" not in calls assert out["busyAfter"] is False assert out["otherProject"] == "p2" and out["otherCalls"] == [] # 프로젝트마다 따로 def test_3_고친_것_없고_단계_완료면_바로_이동(): shared = _read("A00_Common/design/design_shared.ts") fn = shared[shared.index("async function alreadyConfirmed(") :] fn = fn[: fn.index("\n}\n")] assert "designSaveBusy(projectId) || designDirty(projectId)" in fn assert "await pendingRouteSave(projectId)" in fn assert 's.state === "COMPLETE"' in fn and "done(2) && !!done(3)" in fn go = shared[shared.index("export function confirmAndGo(") :] go = go[: go.index("\n}\n")] assert ( "skip ? goToWorkflowStage(shared.projectId, route) : saveSharedDesign(shared, true, route)" in go ) # 줄 세움 = [저장] · 확정 한 곳 assert ( "await queueDesignSave(shared.projectId, confirm, () => saveOnce(shared, confirm, target));" in shared ) def test_결과_한_줄_걷음(): apply = _read("B05_Profile/B05_Profile_UI_RouteEdit_Apply.ts") save = _read("A00_Common/design/design_save.ts") assert "routeSaveSummary" not in apply and "routeSaveSummary" not in save assert "outsideNotice" not in save # 노선 밖 = 편집 중 경고만(저장 뒤 되풀이 없음) assert "noticeDroppedEdits(response)" in save # 계획고 편집 지움 알림은 그대로(498-2 보류)