"""349 배수유역 자동/수동 흐름 — 화면 파일(`B05_Profile_UI_Drainage_Live` · `_Run` · `_Suggest`)을 가짜 DOM · 시계 · fetch 로 돌림(`helper_349_drainage_live_flow.cjs`). - 수동: 꼭짓점 옮김 = 흐림 + [업데이트] · [업데이트] 뒤 묶음이 같은 노선 · 관으로 다시 돌아도 답을 받음 (입력 지문) · 답이 오면 흐림 · 「계산 중」 사라짐 · 제안 「제안 N건」 · [적용]. - 요청 중 또 옮기면 앞 요청 끊고 다시 흐림. - 노선 [저장] 뒤 서버 답이 「낡음」 = 수동은 흐림 + [업데이트] · 자동은 3 s 뒤 조용히. - ⚙ 자동: 흐림 없음 · 3 s 조용한 뒤 셈 · 그 사이 또 옮기면 처음부터 · [업데이트] 막 없음(349-0e). - 낡은 동안 제안 [적용] 끔(두 모드) · 새 답이 오면 다시 켬(349-0e). - 되돌리기로 초안이 없어지면 저장분 다시 받기 · 그 답의 낡음대로 · 초안용 답 끊음 · 자동도 3 s 셈 대신(349-0i). """ 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 = "노선이 바뀌어 제안이 낡음 — 새 계산 뒤 적용" def state(stale, veil, busy, fetches, auto=False): return { "stale": stale, "blur": stale and not auto, # 자동 = 흐림 없음 "veil": veil, "busy": busy, "fetches": fetches, "applyOff": stale, } def look(row): return {k: v for k, v in row.items() if k != "applyTitle"} def test_manual_update_answer_lands_after_same_rerun(r): assert look(r["moved"]) == state(True, True, False, 0) assert look(r["sameAgain"]) == state(True, True, False, 0) # 판 번호만 바뀜 — 다시 안 흐림 assert r["sent"]["fetches"] == 1 and r["sent"]["busy"] and not r["sent"]["veil"] rerun = r["rerunWhileBusy"] assert not rerun["aborted"] and rerun["busy"] # 같은 입력 다시 돎 — 요청 그대로 assert look(r["answered"]) == {**state(False, False, False, 1), "applied": 1} def test_suggestions_and_apply(r): assert r["suggest"]["shown"] and r["suggest"]["title"] == "제안 2건" assert len(r["suggest"]["rows"]) == 2 points = {p["chainage_m"]: p for p in r["applied"]["points"]} assert points[100]["options"]["pipe_diameter_mm"] == 1200 assert points[200]["source"] == "spacing" assert r["applied"]["remembered"] == 1 def test_move_while_busy_cancels(r): moved = r["movedWhileBusy"] assert moved["aborted"] and moved["veil"] and not moved["busy"] assert look(r["fresh"]) == state(False, False, False, 3) def test_stale_blocks_apply(r): assert r["movedWhileBusy"]["applyOff"] assert r["movedWhileBusy"]["applyTitle"] == STALE_TITLE assert r["staleApply"] == 1 # 눌러도 옛 제안이 안 들어감(앞 [적용] 한 번뿐) assert not r["fresh"]["applyOff"] and r["fresh"]["applyTitle"] != STALE_TITLE def test_saved_stale_shows_update(r): assert {k: v for k, v in look(r["savedStale"]).items() if k != "status"} == state( True, True, False, 3 ) assert r["savedStale"]["status"] == "배수유역 낡음" def test_auto_mode(r): assert look(r["autoSwitch"]) == state( True, False, False, 3, True ) # 자동 — 흐림 없음 · [업데이트] 없음 assert r["auto2999"]["fetches"] == 3 assert r["auto3000"]["fetches"] == 4 and r["auto3000"]["route"] is None assert r["auto3000"]["busy"] and not r["auto3000"]["blur"] # 계산 중 = 구석 표시만 assert look(r["autoDone"]) == state(False, False, False, 4) assert look(r["autoRestart"]) == state( True, False, False, 4, True ) # 2 s + 2 s — 처음부터 다시 3 s assert look(r["autoMoved"]) == {**state(False, False, False, 5), "mode": "auto"} assert look(r["autoSavedStale"]) == state(True, False, False, 5, True) assert look(r["autoSavedDone"]) == state(False, False, False, 6) def test_undo_back_to_saved(r): # 자동 — 끌기 뒤 1 s 에 되돌리기: 3 s 셈 없음 · 저장분 하나 · 답 오면 낡음 풂 assert look(r["autoUndoWaiting"]) == {**state(True, False, False, 6, True), "savedLoads": 2} assert r["autoUndoNoFetch"]["fetches"] == 6 assert look(r["autoUndo"]) == {**state(False, False, False, 6), "savedLoads": 2} # 수동 — [업데이트] 요청 중 되돌리기: 초안용 요청 끊음 · 저장분 답 올 때까지 흐림 그대로 waiting = r["undoWaiting"] assert waiting["aborted"] and waiting["stale"] and waiting["blur"] and not waiting["busy"] assert look(r["undo"]) == {**state(False, False, False, 7), "savedLoads": 3} # 저장분 자체가 낡음 = 흐림 · [업데이트] · [적용] 꺼짐 그대로 assert look(r["undoSavedStale"]) == { **state(True, True, False, 7), "savedLoads": 4, "mode": "manual", } def test_input_key(r): assert look(r["keys"]) == { "rev": True, "order": True, "node": False, "pipe": False, "id": False, "saved": True, }