- 필터 = 업체 분류 · 점진 TIN · 둘의 다듬기 판만 · 분석 허용 목록 = Ground 등록부(available_filters) 한 벌 - 표현 = DTM · implicit 만 · 평활 = DTM 만 · config 기본 목록 · PRECOMPUTE · 설정 사전 맞춤(설정 지문 바뀜 — 768 에서 다시 지음) - 등고선 = 격자 · 굳힌 격자만(삼각망 굽기 · 외곽 맞춤 · implicit RBF 폴백 걷음) · 표본기 · 경로 찾기 tin · nurbs · meshfree 갈래 걷음 - 수치지형도 = 라플라스만(TIN 둘 · TPS · ANUDEM형 · 다중해상도 걷음) · B04 단추 하나 - 안 부르는 길 — B04 JSON 점군 GET · ground-stats GET + TS · B07 옛 계획평면도(라이다) 짓기 · lidar_source · LIDAR_ID · plan_lidar 종류 이름 · SOURCE_FILTER_LABELS · MODEL_METHODS(py) · MESH_* 설정 - 옛 manifest 의 지운 갈래 줄은 DB 로 되살리지 않음 · 옛 csf 행은 미리보기 · 등고선 그대로 열림 · 지운 이름 요청 = 400 - 낡은 글 — Ground · Pipeline 머리 · WF1 「없으면 csf」 · B07 음영 기본 「csf」 → 빈 값 · 등고선 폴백 「csf」 → 404 · B04 3D 보기 처음값 · B05 Schema · B06 Prebuild · 씨닝 설명 - 시험 지움 2 — test_b04_contour_raster · test_b04_contour_equivalence(지운 삼각망 굽기 · 없는 프로젝트를 봐 건너뛰던 것) - 시험 덜어냄 — test_b04_ground_filters CSF · 격자 최저 네 경우(지운 필터) - 시험 고침 — 허용 목록 · 형 검사에 걸린 csf · tin · grid_min_z · meshfree · tin_sheet 견본을 ptd · implicit · laplace 로(54_22 · 238_1 · 443 · 342_2 · 346_3 · 351_1 · 442_2 · 271 + helper · 344_1 · 133_1 · 343_2 · 637 · 754_1 · 763_1 깨진 tin → 깨진 implicit · 763_3 · b05 둘 · b06 · sheet_surface TIN(도엽선) 경우 지움) · 이름만 바꿈(273_2 · 343_1 · 343_3 · 624_1) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
213 lines
7.7 KiB
Python
213 lines
7.7 KiB
Python
"""PLAN 443 — 저장 누락 ① 구조물 실패 · 상단측 재전송 · 확정 하위 실패(H6 · M3 · M6).
|
|
|
|
- H6 구조물 저장 실패(409 · 그 밖) = 던짐(저장 한 줄기가 구조물 차례에서 멈춤) · 초안 남음 · 줄 안 막힘.
|
|
- M3 상단측 차례 성공 = uphill 세션 비움(다음 [저장]이 다시 안 보냄) · 실패 = 세션 그대로.
|
|
- M6 B05 확정 하위(비정규 측점 횡단 · 상단측 병합) 실패 = 응답 failed_parts · 화면이 던짐.
|
|
Node 헬퍼 = `helper_443_save_stops.cjs`.
|
|
|
|
./venv/Scripts/python.exe -m pytest resources/tester/test_443_save_stops.py -q
|
|
"""
|
|
|
|
import asyncio
|
|
import json
|
|
import subprocess
|
|
from pathlib import Path
|
|
from uuid import uuid4
|
|
|
|
import pytest
|
|
|
|
from B05_Profile import B05_Profile_Router_Lifecycle as lifecycle
|
|
from B05_Profile.B05_Profile_Schema import RouteConfirmRequest
|
|
|
|
HERE = Path(__file__).parent
|
|
ROOT = HERE.parents[1]
|
|
|
|
|
|
@pytest.fixture(scope="module")
|
|
def out():
|
|
proc = subprocess.run(
|
|
["node", str(HERE / "helper_443_save_stops.cjs")],
|
|
capture_output=True,
|
|
text=True,
|
|
encoding="utf-8",
|
|
timeout=60,
|
|
)
|
|
assert proc.returncode == 0, proc.stderr
|
|
return json.loads(proc.stdout)
|
|
|
|
|
|
# ── H6 ───────────────────────────────────────────────────────────────────────
|
|
|
|
|
|
def test_구조물_409_던짐_초안_남음(out):
|
|
assert "먼저 저장" in out["conflict"]
|
|
assert out["pendingAfterConflict"] == [{"structure_id": "s1"}]
|
|
|
|
|
|
def test_구조물_실패_뒤_줄이_안_막힘(out):
|
|
assert out["boom"] == "서버 500" and out["pendingAfterBoom"] == [{"structure_id": "s1"}]
|
|
assert out["ok"] == "ok" and out["pendingAfterOk"] is None
|
|
assert out["puts"] == [3, 3, 3] # 세 번 다 나감
|
|
|
|
|
|
def test_구조물_차례가_토스트로_삼키지_않음():
|
|
page = (ROOT / "B05_Profile" / "B05_Profile_UI_Page_Structures.ts").read_text("utf-8")
|
|
body = page.split("async function persistStructures(")[1].split("\n }\n")[0]
|
|
assert "showToast(error" not in body and "return;\n" not in body.split("catch (error)")[1]
|
|
|
|
|
|
# ── M3 · M6 화면 ─────────────────────────────────────────────────────────────
|
|
|
|
|
|
def test_상단측_실패면_세션_그대로_성공이면_비움(out):
|
|
"""472-6 — 상단측 = 한 본문 `uphill` 칸 · 본문 = 세션 · 세션 비움 = 서버가 다 저장한 뒤 조각 정리만
|
|
(실패면 정리가 안 돎 = 세션 그대로 · 옛 창구 `flushUphillOverrides` 걷음)."""
|
|
assert out["exported"] == ["routePartsBody"] # 옛 창구 둘 걷음 · 본문 한 벌만
|
|
assert out["uphillAfterBody"] == {"40.000": "right"} and out["bodies"] == []
|
|
fetch = (ROOT / "B05_Profile" / "B05_Profile_Api_Fetch.ts").read_text("utf-8")
|
|
assert "uphill_overrides: uphillOverridesOf(projectId)," in fetch # 본문 = 세션
|
|
save = (ROOT / "A00_Common" / "design" / "design_save.ts").read_text("utf-8")
|
|
part = save[save.index('case "uphill":') :]
|
|
assert 'after: () => clearState("uphill", projectId),' in part[:500]
|
|
assert "for (const key of SAVE_PART_KEYS) await parts[key]?.after?.(" in save # 성공 뒤에만
|
|
|
|
|
|
def test_확정_하위_실패면_던짐(out):
|
|
"""472-6 — 하위 차례(비정규 측점 횡단 · 상단측 병합) 실패 = 서버 한 본문이 상단측 차례에서 실패 · 되돌림."""
|
|
server = (ROOT / "B06_Section" / "B06_Section_Router_DesignSave.py").read_text("utf-8")
|
|
assert 'if parts.get("failed_parts"):' in server
|
|
assert 'raise StepFailed("uphill", " · ".join(parts["failed_parts"]) + " 실패")' in server
|
|
|
|
|
|
def test_종단_보기_상단측_차례도_같은_창구():
|
|
actions = (ROOT / "B05_Profile" / "B05_Profile_UI_Page_Actions.ts").read_text("utf-8")
|
|
# 기본 조각 한 벌(463 · 472-6 · 종단 보기 몫 없음 · 472-5) — 상단측은 세션에서 읽음(메모리 사본 없음)
|
|
assert "uphill:" not in actions and "flushRouteParts" not in actions
|
|
fetch = (ROOT / "B05_Profile" / "B05_Profile_Api_Fetch.ts").read_text("utf-8")
|
|
assert "uphill_overrides: uphillOverridesOf(projectId)," in fetch
|
|
assert "confirmRoute(" not in actions
|
|
|
|
|
|
# ── M6 서버 ──────────────────────────────────────────────────────────────────
|
|
|
|
|
|
class _Cursor:
|
|
async def __aenter__(self):
|
|
return self
|
|
|
|
async def __aexit__(self, *exc):
|
|
return False
|
|
|
|
|
|
class _Connection:
|
|
def __init__(self):
|
|
self.committed = False
|
|
|
|
async def begin(self):
|
|
pass
|
|
|
|
async def commit(self):
|
|
self.committed = True
|
|
|
|
async def rollback(self):
|
|
pass
|
|
|
|
def cursor(self):
|
|
return _Cursor()
|
|
|
|
|
|
class _Pool:
|
|
def __init__(self, connection):
|
|
self.connection = connection
|
|
|
|
def acquire(self):
|
|
connection = self.connection
|
|
|
|
class _Ctx:
|
|
async def __aenter__(self):
|
|
return connection
|
|
|
|
async def __aexit__(self, *exc):
|
|
return False
|
|
|
|
return _Ctx()
|
|
|
|
|
|
def _patch(monkeypatch, connection, *, append, longitudinal, merge):
|
|
async def latest(*_a):
|
|
return {"id": 7, "status": "DRAFT"}
|
|
|
|
async def noop(*_a, **_k):
|
|
return None
|
|
|
|
async def storage(*_a):
|
|
return "p"
|
|
|
|
async def get_long(*_a):
|
|
return longitudinal
|
|
|
|
monkeypatch.setattr(lifecycle, "get_db_pool", lambda: _Pool(connection))
|
|
monkeypatch.setattr(lifecycle, "get_latest_route", latest)
|
|
monkeypatch.setattr(lifecycle, "confirm_route", noop)
|
|
monkeypatch.setattr(lifecycle, "complete_stage", noop)
|
|
monkeypatch.setattr(lifecycle, "_append_irregular_cross_sections", append)
|
|
monkeypatch.setattr(lifecycle, "get_project_storage_relative_path", storage)
|
|
monkeypatch.setattr(lifecycle, "get_longitudinal_section", get_long)
|
|
monkeypatch.setattr(lifecycle, "resolve_stored_project_path", lambda p: p)
|
|
monkeypatch.setattr(lifecycle, "_merge_uphill_overrides_into_longitudinal", merge)
|
|
monkeypatch.setattr(lifecycle, "sync_uphill_overrides_into_designs", noop)
|
|
|
|
|
|
def _boom(*_a, **_k):
|
|
raise RuntimeError("boom")
|
|
|
|
|
|
async def _aboom(*_a, **_k):
|
|
raise RuntimeError("boom")
|
|
|
|
|
|
_REQUEST = RouteConfirmRequest(
|
|
filter_key="ptd",
|
|
method="implicit",
|
|
surface_model_id=1,
|
|
uphill_overrides=[{"chainage_m": 40.0, "side": "right"}],
|
|
)
|
|
|
|
|
|
def test_확정_하위_실패를_응답에_실음(monkeypatch):
|
|
connection = _Connection()
|
|
_patch(
|
|
monkeypatch,
|
|
connection,
|
|
append=_aboom,
|
|
longitudinal={"longitudinal_file_path": "l.json"},
|
|
merge=_boom,
|
|
)
|
|
result = asyncio.run(lifecycle.confirm_latest_route(uuid4(), _REQUEST, False))
|
|
assert result.failed_parts == ["비정규 측점 횡단", "상단측 병합"]
|
|
assert connection.committed # 확정 자체는 진행(비치명)
|
|
|
|
|
|
def test_종단_없으면_상단측_병합_실패(monkeypatch):
|
|
async def ok(*_a, **_k):
|
|
return None
|
|
|
|
_patch(monkeypatch, _Connection(), append=ok, longitudinal=None, merge=lambda *a: None)
|
|
result = asyncio.run(lifecycle.confirm_latest_route(uuid4(), _REQUEST, False))
|
|
assert result.failed_parts == ["상단측 병합(종단 없음)"]
|
|
|
|
|
|
def test_다_되면_빈_목록(monkeypatch):
|
|
async def ok(*_a, **_k):
|
|
return None
|
|
|
|
_patch(
|
|
monkeypatch,
|
|
_Connection(),
|
|
append=ok,
|
|
longitudinal={"longitudinal_file_path": "l.json"},
|
|
merge=lambda *a: None,
|
|
)
|
|
result = asyncio.run(lifecycle.confirm_latest_route(uuid4(), _REQUEST, False))
|
|
assert result.failed_parts == []
|