- 서버: sections save · confirm 이 최신 아닌 노선 id 를 409 로 거절(옛 노선 재계산 없음) - 화면: [저장] · [확정] 앞에서 서버 노선과 대조 · 다르면 아무것도 안 보내고 페이지 새로 세움 - 화면: 종단 보기 신선도 확인에서 노선이 바뀌면 선만 다시 그리지 않고 페이지 새로 세움 - 시험: test_76_stale_route_save · 기존 저장 시험에 최신 노선 조회 가짜값 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EYJJRCfHfdCGNhmXTjQKV7
119 lines
4.5 KiB
Python
119 lines
4.5 KiB
Python
"""노선 [확인] 뒤 옛 노선 id 로 저장이 나가지 않는지 (PLAN 76-1).
|
|
|
|
사용자 실측(226fc805): 노선 264 → 267 로 갈린 뒤 [저장]이 `sections/264/save` 로 나가 서버가
|
|
옛 노선 264 를 다시 계산했다. 서버는 최신 아닌 노선 저장을 409 로 거절하고, 화면은 저장 앞에서
|
|
서버 노선과 대조해 다르면 아무것도 안 보내고 페이지를 새로 세운다.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import asyncio
|
|
import json
|
|
import sys
|
|
from pathlib import Path
|
|
from typing import Any
|
|
from uuid import UUID
|
|
|
|
import pytest
|
|
|
|
PROJECT_ROOT = Path(__file__).resolve().parents[2]
|
|
if str(PROJECT_ROOT) not in sys.path:
|
|
sys.path.insert(0, str(PROJECT_ROOT))
|
|
|
|
import B06_Section.B06_Section_Router_Confirm as confirm # noqa: E402
|
|
|
|
PID = UUID("fa76c162-71c7-46e5-a95d-fb3930665a45")
|
|
|
|
|
|
class _Conn:
|
|
async def begin(self) -> None:
|
|
return None
|
|
|
|
async def commit(self) -> None:
|
|
return None
|
|
|
|
async def rollback(self) -> None:
|
|
return None
|
|
|
|
|
|
class _Acquire:
|
|
async def __aenter__(self) -> _Conn:
|
|
return _Conn()
|
|
|
|
async def __aexit__(self, *_: Any) -> None:
|
|
return None
|
|
|
|
|
|
class _Pool:
|
|
def acquire(self) -> _Acquire:
|
|
return _Acquire()
|
|
|
|
|
|
def _async(value: Any):
|
|
async def _inner(*_: Any, **__: Any) -> Any:
|
|
return value
|
|
|
|
return _inner
|
|
|
|
|
|
def _wire(monkeypatch: pytest.MonkeyPatch, latest: int) -> list[str]:
|
|
"""최신 노선을 `latest` 로 두고, 저장 본체가 불렸는지 적는 목록을 돌려준다."""
|
|
called: list[str] = []
|
|
|
|
async def _edits(*_: Any, **__: Any) -> None:
|
|
called.append("edits")
|
|
|
|
async def _recompute(*_: Any, **__: Any) -> None:
|
|
called.append("recompute")
|
|
|
|
monkeypatch.setattr(confirm, "get_db_pool", lambda: _Pool())
|
|
monkeypatch.setattr(
|
|
confirm, "get_longitudinal_section", _async({"longitudinal_file_path": "long.json"})
|
|
)
|
|
monkeypatch.setattr(confirm, "get_workflow_route_context", _async({"route_id": latest}))
|
|
monkeypatch.setattr(confirm, "get_project_storage_relative_path", _async("1/3/proj"))
|
|
monkeypatch.setattr(confirm, "get_cross_section_chainages", _async([0.0, 20.0]))
|
|
monkeypatch.setattr(confirm, "get_cross_sections_missing_design_chainages", _async([]))
|
|
monkeypatch.setattr(confirm, "resolve_stored_project_path", lambda _p: str(PROJECT_ROOT))
|
|
monkeypatch.setattr(confirm, "_rowless_station_chainages", lambda *_a, **_k: [])
|
|
monkeypatch.setattr(confirm, "_apply_section_edits", _edits)
|
|
monkeypatch.setattr(confirm, "_recompute_stored_designs", _recompute)
|
|
return called
|
|
|
|
|
|
@pytest.mark.parametrize("endpoint", ["save_sections", "confirm_sections"])
|
|
def test_옛_노선_저장은_409(monkeypatch: pytest.MonkeyPatch, endpoint: str) -> None:
|
|
called = _wire(monkeypatch, latest=267)
|
|
response = asyncio.run(getattr(confirm, endpoint)(project_id=PID, route_id=264, request=None))
|
|
assert response.status_code == 409
|
|
assert json.loads(response.body)["latest_route_id"] == 267
|
|
assert called == [] # 저장 · 옛 노선 재계산 둘 다 안 돎
|
|
|
|
|
|
def test_최신_노선_저장은_그대로(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
called = _wire(monkeypatch, latest=267)
|
|
response = asyncio.run(confirm.save_sections(project_id=PID, route_id=267, request=None))
|
|
assert getattr(response, "confirmed", None) is False, response
|
|
assert called == ["edits", "recompute"]
|
|
|
|
|
|
def _read(path: str) -> str:
|
|
return (PROJECT_ROOT / path).read_text(encoding="utf-8")
|
|
|
|
|
|
def test_화면은_저장_앞에서_노선을_대조한다() -> None:
|
|
"""[저장] · [확정] 첫 줄이 서버 노선 대조 — 다르면 아무 단계도 안 보내고 페이지를 새로 세움."""
|
|
source = _read("A00_Common/design/design_shared.ts")
|
|
body = source.split("export async function saveSharedDesign", 1)[1]
|
|
assert body.index("routeChanged(shared)") < body.index("saveDesign(")
|
|
guard = source.split("async function routeChanged", 1)[1].split("\n}\n", 1)[0]
|
|
assert "fetchCurrentRouteId" in guard and "remountRoute" in guard
|
|
|
|
|
|
def test_신선도_확인에서_노선이_바뀌면_페이지를_새로_세운다() -> None:
|
|
"""캐시로 그린 뒤 서버 노선이 다르면 선만 다시 그리지 않음 — 횡단 보기 · 측점 · 구조물이 옛 노선."""
|
|
source = _read("A00_Common/design/design_view_profile.ts")
|
|
verify = source.split("latestSource.verify(latestResponse", 1)[1].split("});", 1)[0]
|
|
assert "fresh.route?.id !== latestResponse.route?.id" in verify
|
|
assert verify.index("remountRoute(") < verify.index("renderLatest(fresh)")
|