Files
Aislo/resources/tester/test_349_0u_save_rebase.py
T
eomsangdonandClaude Sonnet 5.5 026ca74c52 test(507-4): 검증 스크립트 정비 — 계산 자리 화면 시험 안전장치 · 505-3 교각점 요소 자리 · 검증_sub7 시험 12 파일 지금 검증 프로젝트로 · 공용 TS 불러오기 첫 걸음
- 309-2 화면 시험: 5177 이 8004 로 갈 때만 돎(남의 마스터 안 씀) · 실패해도 판 되돌림(finally) · 읽기는 서버 응답으로 판정
- 505-3 첫 시험: 픽셀 (1199,296) 대신 캔버스 교각점 색으로 찾음 · 포트 · 프로젝트는 환경 변수로
- 349 계열 · 346-2 · 356-4: helper_verify_project 가 OWNERS.md 검증_subN 중 가장 오래 안 바뀐 프로젝트를 읽기만 · 없으면 까닭 글로 건너뜀 · 낡은 기대 xfail
- helper_ts_stub_loader 공용 불러오기 + 새 시험 · 헬퍼 3 곳 옮김

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-21 16:02:51 +09:00

127 lines
5.5 KiB
Python

"""PLAN 349-0u 덧 sub7 — 노선을 저장한 [저장]에서 세션 관 초안이 서버 따라감을 덮지 않음.
· 초안 관이 노선 저장 전 정본 자리 그대로(따라감 전 값) = 서버가 옮긴 자리(옵션 고침은 얹음)
· 사용자가 옮긴 · 새로 놓은 관 = 그대로 · 지운 관 = 없음 · 서버가 새로 놓은 교차 관 = 붙음
· 노선 저장 없음 = 초안 그대로 · 초안 없음 = 관 GET · PUT 없음
· 서버 따라감 = 실제 `project_pipe_points`(검증_sub7 관 · 노선 꼭짓점 하나 옮김)
짝: `helper_349_0u_save_rebase.cjs`(실제 `design_save.ts` 를 Node 로 · 서버 창구만 가짜).
"""
from __future__ import annotations
import copy
import csv
import json
import math
import subprocess
import sys
import tempfile
from pathlib import Path
import pytest
HERE = Path(__file__).resolve().parent
ROOT = HERE.parents[1]
if str(ROOT) not in sys.path:
sys.path.insert(0, str(ROOT))
from helper_verify_project import ROUTE_SET, SKIP_REASON, find_project # noqa: E402
from common_util.common_util_drainage_pipes import ( # noqa: E402
parse_pipe_points,
project_pipe_points,
)
from common_util.common_util_route_geometry import RouteVertex # noqa: E402
pytestmark = pytest.mark.skipif(
not (ROOT / "config" / "node_modules" / "typescript").is_dir(),
reason="프론트엔드 의존성 미설치",
)
PROJECT = find_project(*ROUTE_SET) # 지금 있는 검증 프로젝트(읽기만)
PIPES = PROJECT / "B04_PreProcess/drainage/edits/pipe_points.json"
LINE = PROJECT / "B05_Profile/route/planned_route.csv"
def _vertices(line: list[list[float]]) -> list[RouteVertex]:
walked, out = 0.0, []
for i, (x, y) in enumerate(line):
walked += math.dist(line[i - 1], (x, y)) if i else 0.0
out.append(RouteVertex(x=x, y=y, z=0.0, chainage_m=walked))
return out
@pytest.fixture(scope="module")
def case() -> dict:
if not (PIPES.is_file() and LINE.is_file()):
pytest.skip(SKIP_REASON)
with LINE.open(encoding="utf-8") as handle:
line = [[float(r["x"]), float(r["y"])] for r in csv.DictReader(handle)]
raw = json.loads(PIPES.read_text(encoding="utf-8"))["points"]
if len(raw) < 6: # 시험이 관 여섯 개째(p5)까지 만짐
pytest.skip("검증 프로젝트 관 6 개 미만")
before = [{**pipe, "id": f"p{i}"} for i, pipe in enumerate(raw)]
# 서버 노선 저장 — 앞쪽 꼭짓점을 서쪽으로 크게 옮긴 새 노선에 좌표 투영 + 새 교차 관 하나
moved = copy.deepcopy(line)
moved[1] = [moved[1][0] - 60.0, moved[1][1]]
points = project_pipe_points(parse_pipe_points(before), _vertices(moved))
after = [point.as_dict() for point in points]
after.append({"chainage_m": 30.0, "source": "spacing", "id": "p-new", "x": 1.0, "y": 2.0})
# 세션 초안 = 정본 PUT 꼴(좌표 뺌 · 옛 노선 누가거리) — p1 옵션 고침 · p3 사용자가 7 m 옮김 ·
# p5 지움 · 새 관 하나(id 없음)
draft = [{k: v for k, v in pipe.items() if k not in ("x", "y")} for pipe in before]
draft[1]["options"] = {**draft[1].get("options", {}), "pipe_count": 3}
draft[3]["chainage_m"] += 7.0
del draft[5]
draft.append({"chainage_m": 333.0, "source": "user"})
job = {"before": before, "after": after, "draft": draft}
with tempfile.TemporaryDirectory() as folder:
path = Path(folder) / "job.json"
path.write_text(json.dumps(job), encoding="utf-8")
proc = subprocess.run( # noqa: S603 — 고정 실행 파일
["node", str(HERE / "helper_349_0u_save_rebase.cjs"), str(path)],
capture_output=True,
text=True,
encoding="utf-8",
timeout=120,
)
assert proc.returncode == 0, proc.stderr
return {**job, "out": json.loads(proc.stdout)}
def test_노선_저장_뒤_관_차례가_서버_따라감_위에(case):
r = case["out"]["rebased"]
assert r["ok"] and r["calls"] == ["GET", "ROUTE", "GET", "PUT"], r["calls"]
sent = {p.get("id", "user"): p for p in r["sent"][0]}
after = {p["id"]: p for p in case["after"]}
before = {p["id"]: p for p in case["before"]}
assert set(sent) == ({p["id"] for p in case["before"]} - {"p5"}) | {"p-new", "user"}
for pid in sent.keys() - {"p3", "user"}:
assert sent[pid]["chainage_m"] == after[pid]["chainage_m"], pid # 서버 자리
moved = [p for p in before if p in after and after[p]["chainage_m"] != before[p]["chainage_m"]]
assert moved # 실제로 서버가 옮긴 관이 있음
assert sent["p1"]["options"]["pipe_count"] == 3 # 사용자 옵션 고침은 얹음
assert sent["p3"]["chainage_m"] == before["p3"]["chainage_m"] + 7.0 # 사용자가 옮긴 자리
assert sent["user"]["chainage_m"] == 333.0
chain = [p["chainage_m"] for p in r["sent"][0]]
assert chain == sorted(chain)
assert r["left"] is None # 내보낸 뒤 초안 비움
def test_초안_없으면_관_안_건드림(case):
r = case["out"]["noDraft"]
assert r["ok"] and r["calls"] == ["ROUTE"], r["calls"]
def test_노선_그대로면_초안_그대로(case):
r = case["out"]["noRoute"]
assert r["ok"] and r["calls"] == ["PUT"], r["calls"]
assert r["sent"][0] == case["draft"]
def test_안_옮긴_관은_정본_좌표(case):
a, b, c = case["out"]["placed"]
assert (a["x"], a["y"]) == (40.4, 3) # 같은 id · 같은 누가거리 = 정본 좌표(노선 밖이어도)
assert (b["x"], b["y"]) == (60, 0) # 옮긴 관 = 노선 위 그 누가거리
assert (c["x"], c["y"]) == (70, 0)