"""349 · 326-6 배수유역 [업데이트] 흐름 — 화면 파일(`B05_Profile_UI_Drainage_Live` · `_Run` · `_Suggest`)을 가짜 DOM · 시계 · fetch 로 돌림(`helper_349_drainage_live_flow.cjs`). - 꼭짓점 옮김 = 옛 그림 그대로 · [업데이트] 단추에 낡음 점만 · 저절로 셈 없음(시계 60 s 흘려도). - [업데이트] = 뒤에서 조용히 셈(단추만 「계산 중…」 · 구석 표시) · 묶음이 같은 입력으로 다시 돌아도 답을 받음. - 낡지 않아도 [업데이트] = 다시 셈(326-6) · 약속은 답이 오르면 참(B06 [>] 확인 창 뒤 이동). - 요청 중 또 옮기면 앞 요청 끊고 다시 낡음 · 낡은 동안 제안 [적용] · [자동 제안] 끔. - 노선 [저장] 뒤 서버 답이 「낡음」 = 낡음 표시 · 저절로 셈 없음. - 되돌리기로 초안이 없어지면 저장분 다시 받기 · 그 답의 낡음대로 · 초안용 답 끊음(349-0i) · 서버에 끊김 알림 · 표시는 저장분의 마지막 낡음으로 바로(520-2 · 저장 노선 [업데이트] 뒤 = 낡음 즉시 꺼짐). """ import json import os import subprocess import pytest HERE = os.path.dirname(os.path.abspath(__file__)) @pytest.fixture(scope="module") def r(): proc = subprocess.run( ["node", os.path.join(HERE, "helper_349_drainage_live_flow.cjs")], capture_output=True, text=True, timeout=60, encoding="utf-8", ) assert proc.returncode == 0, proc.stderr return json.loads(proc.stdout) STALE_TITLE = "노선이 바뀌어 제안이 낡음 — 새 계산 뒤 적용" IDLE = "업데이트" BUSY = "계산 중…" def state(stale, busy, fetches): return { "stale": stale, "pending": stale, # 낡음 = 단추 점(흐림 · 덮개 없음) "off": busy, # 셈 동안 단추만 잠금 "label": BUSY if busy else IDLE, "busy": busy, "fetches": fetches, "applyOff": stale, } def look(row, *skip): return {k: v for k, v in row.items() if k not in ("applyTitle", *skip)} def test_moved_keeps_old_picture_no_auto(r): assert look(r["start"]) == {**state(False, False, 0), "applyOff": True} # 제안 없음 assert look(r["moved"]) == state(True, False, 0) assert look(r["sameAgain"]) == state(True, False, 0) # 판 번호만 바뀜 assert look(r["noAuto"]) == state(True, False, 0) # 60 s 지나도 셈 없음 def test_update_answer_lands_after_same_rerun(r): assert look(r["sent"], "body") == state(True, True, 1) assert r["sent"]["body"]["route"]["rev"] == 2 # 지금 초안 노선으로 rerun = r["rerunWhileBusy"] assert not rerun["aborted"] and rerun["busy"] # 같은 입력 다시 돎 — 요청 그대로 assert look(r["answered"], "applied") == state(False, False, 1) assert r["answered"]["applied"] == 1 def test_suggestions_and_apply(r): # 관 자리 후보(200)는 제안 목록에 안 올림 — [자동 제안] 목록 몫(510) assert r["suggest"]["shown"] and r["suggest"]["title"] == "제안 1건" assert len(r["suggest"]["rows"]) == 1 points = {p["chainage_m"]: p for p in r["applied"]["points"]} assert points[100]["options"]["pipe_diameter_mm"] == 1200 assert 200 not in points assert r["applied"]["remembered"] == 1 assert r["records"] == 1 # [적용] 한 번 = 사진첩 한 걸음 def test_move_while_busy_cancels(r): moved = r["movedWhileBusy"] assert moved["aborted"] and look(moved, "aborted") == state(True, False, 2) assert r["promised"] is True assert look(r["fresh"]) == state(False, False, 3) def test_update_always_recomputes(r): assert look(r["again"], "route") == {**state(False, True, 4), "applyOff": False} assert look(r["againDone"]) == state(False, False, 4) def test_stale_blocks_apply(r): assert r["movedWhileBusy"]["applyTitle"] == STALE_TITLE assert r["staleApply"] == 1 # 눌러도 옛 제안이 안 들어감(앞 [적용] 한 번뿐) assert r["fresh"]["applyTitle"] != STALE_TITLE assert r["proposeStale"] == {"off": True, "title": "B05_Drainage_Btn_Propose_Stale"} assert r["proposeFresh"] == {"off": False, "title": "B05_Drainage_Btn_Propose_Tip"} def test_saved_stale_marks_only(r): assert look(r["savedStale"], "status") == state(True, False, 4) assert r["savedStale"]["status"] == "배수유역 낡음" assert look(r["savedStaleNoAuto"]) == state(True, False, 4) assert look(r["savedDone"]) == state(False, False, 5) def test_undo_back_to_saved(r): waiting = r["undoWaiting"] assert waiting["aborted"] and look(waiting, "aborted", "savedLoads") == state(False, False, 6) assert r["undoCancelled"] assert look(r["undo"], "savedLoads") == state(False, False, 6) assert r["undo"]["savedLoads"] == 2 assert look(r["undoSavedStale"], "savedLoads") == state(True, False, 6) assert r["undoSavedStale"]["savedLoads"] == 3 def test_input_key(r): assert r["keys"] == { "rev": True, "order": True, "node": False, "pipe": False, "id": False, "saved": True, }