- 화면이 처음 열릴 때 「낡음」을 모르면 [자동 제안]이 409 오류로 끝나던 흠 — 곧바로 drainage/live 로 다시 - 되돌리기가 관 자리만 되살리고 속성은 지금 것을 끌고 가 [배관 배치 초기화] 뒤 되돌려도 세월교가 사라지던 흠 — 되살린 걸음의 관 초안(세션)이 같은 자리면 그 속성 · id 로(adoptDraftPipes) - 시험 — 349 흐름(409 → 다시) · 347 pipe id(초안 속성 되살림) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VmC34fsxxbweEG3RUjs3Mn
161 lines
6.5 KiB
Python
161 lines
6.5 KiB
Python
"""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 200 not in points # 관 자리 후보는 [적용]이 안 더함 — [자동 제안] 몫(369)
|
|
assert r["applied"]["remembered"] == 1
|
|
|
|
|
|
def test_자동_제안_두_방식_초기화_세류선만_한_걸음(r):
|
|
assert r["proposeStale"] == {
|
|
"off": True,
|
|
"title": "B05_Drainage_Btn_Propose_Stale",
|
|
"calls": 0, # 낡은 동안 끔 — 눌러도 요청 없음
|
|
}
|
|
assert r["proposeFresh"] == {"off": False, "title": "B05_Drainage_Btn_Propose_Tip"}
|
|
assert r["propose"] == {
|
|
"autos": ["all", "add", None],
|
|
"points": [1, 1, None], # ① ② = 지금 관 목록 함께 · 초기화 = 목록 없이(세류선만)
|
|
"remembered": 3, # 셋 다 초안
|
|
"records": 3, # 답마다 사진첩 한 걸음
|
|
}
|
|
# 저장 분석이 옛 노선 — 지금 노선으로 셈 길(detail-basins 안 부름) · 같은 초안 · 한 걸음
|
|
assert r["proposeLive"] == {
|
|
"auto": "all",
|
|
"points": 1,
|
|
"resetPoints": None,
|
|
"resetAuto": None,
|
|
"detailCalls": 0,
|
|
"remembered": 6, # 앞 「409 → 다시」 한 번 포함
|
|
"records": 6,
|
|
}
|
|
fallback = r["proposeFallback"]
|
|
assert fallback["sent"] == 1 and fallback["auto"] == "all"
|
|
assert "낡음" not in fallback["status"] # 409 글을 오류로 안 띄움(조용히 다시)
|
|
|
|
|
|
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,
|
|
}
|