Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QND7Co43B24zK2P937rdxt
141 lines
5.8 KiB
Python
141 lines
5.8 KiB
Python
"""256-2 B08 거리이정표 경로 그림 = 전체 경로(사용자 162-7).
|
|
|
|
캡처 · 길찾기는 가짜로:
|
|
① 사슬 = 거래처 → 기준점 사슬(254 bases · to) → 시점 · 화면 `mapMarks` 차례 · 좌표 없는 상자 뺌
|
|
② 구간마다 카카오 길 선 · 수기 · 실패 구간 = 곧은 선 · auto/<경로 id>.png
|
|
③ [저장] 뒤 지문 비교 — 안 바뀜 = 0 · 기준점 · 시점 옮김 = 그 사슬 경로만 · 안 이은 거래처 = 없음 ·
|
|
화면 [길찾기] 그림(거래처 구간만)이 들어오면 다음 [저장]에 전체로
|
|
"""
|
|
|
|
import sys
|
|
from pathlib import Path
|
|
from uuid import uuid4
|
|
|
|
from fastapi import FastAPI
|
|
from fastapi.testclient import TestClient
|
|
|
|
ROOT = Path(__file__).resolve().parents[2]
|
|
if str(ROOT) not in sys.path:
|
|
sys.path.insert(0, str(ROOT))
|
|
|
|
from B08_Quantity import B08_Quantity_Router_Milestone as ms # noqa: E402
|
|
|
|
PNG = b"\x89PNG\r\n\x1a\nfake-route"
|
|
START = [127.5, 35.4]
|
|
BASES = [
|
|
{"id": "b1", "name": "거창IC", "lon": 127.91, "lat": 35.68, "to": "b2"},
|
|
{"id": "b2", "name": "함양JC", "lon": 127.72, "lat": 35.52, "manual": True},
|
|
]
|
|
ROUTES = [
|
|
{"id": "r1", "name": "거창레미콘", "lon": 127.95, "lat": 35.70, "to": "b1"},
|
|
{"id": "r2", "name": "직송", "lon": 127.60, "lat": 35.45},
|
|
{"id": "r3", "name": "안 이음", "lon": 127.61, "lat": 35.46, "linked": False},
|
|
{"id": "r4", "name": "좌표 없음", "to": "b2"},
|
|
]
|
|
DATA = {"bases": BASES, "routes": ROUTES}
|
|
|
|
|
|
class Fake:
|
|
def __init__(self):
|
|
self.shots = [] # (경로 id, lines, marks)
|
|
self.asked = [] # 길찾기 구간
|
|
|
|
def shoot(self, out_dir, route_id, lines, marks):
|
|
self.shots.append((route_id, lines, marks))
|
|
out_dir.mkdir(parents=True, exist_ok=True)
|
|
(out_dir / f"{route_id}.png").write_bytes(PNG)
|
|
return {"saved": True, "reason": None}
|
|
|
|
def directions(self, origin, destination):
|
|
self.asked.append((origin.lon, destination.lon))
|
|
if origin.lon == 127.60: # r2 → 시점 = 길찾기 실패
|
|
return {"km": None, "reason": "실패"}
|
|
return {"km": 1.0, "reason": None, "path": [[origin.lat, origin.lon], [0, 0]]}
|
|
|
|
|
|
def _setup(tmp_path, monkeypatch, start=START):
|
|
fake = Fake()
|
|
box = {"start": start}
|
|
monkeypatch.setattr(ms, "make_route_image", fake.shoot)
|
|
monkeypatch.setattr(ms, "directions_km", fake.directions)
|
|
monkeypatch.setattr(ms, "make_place_image", lambda *a: {"saved": False}) # 256-1 시험
|
|
monkeypatch.setattr(
|
|
ms, "site_info", lambda root, mid: {"start_lonlat": box["start"], "mid_default_m": None}
|
|
)
|
|
return fake, box
|
|
|
|
|
|
def _save(root, data):
|
|
ms.save_data(root, ms.Milestone.model_validate(data))
|
|
ms.refresh_images(root)
|
|
|
|
|
|
def test_사슬_화면_차례():
|
|
data = ms.Milestone.model_validate(DATA).model_dump()
|
|
chain = ms.route_chain(data, data["routes"][0], START)
|
|
assert chain == [
|
|
("거창레미콘", 127.95, 35.70, False),
|
|
("거창IC", 127.91, 35.68, False),
|
|
("함양JC", 127.72, 35.52, True),
|
|
("시점", 127.5, 35.4, False),
|
|
]
|
|
assert [c[0] for c in ms.route_chain(data, data["routes"][3], START)] == ["함양JC", "시점"]
|
|
assert [c[0] for c in ms.route_chain(data, data["routes"][1], None)] == ["직송"]
|
|
|
|
|
|
def test_전체_경로_그림_구간_선(tmp_path, monkeypatch):
|
|
fake, _ = _setup(tmp_path, monkeypatch)
|
|
_save(tmp_path, DATA)
|
|
shots = {rid: (lines, marks) for rid, lines, marks in fake.shots}
|
|
assert sorted(shots) == ["r1", "r2", "r4"] # r3 안 이음 = 없음
|
|
lines, marks = shots["r1"]
|
|
assert [m["name"] for m in marks] == ["거창레미콘", "거창IC", "함양JC", "시점"]
|
|
assert len(lines) == 3
|
|
assert lines[0] == [[35.70, 127.95], [0, 0]] # 카카오 길 선
|
|
assert lines[2] == [[35.52, 127.72], [35.4, 127.5]] # 수기 기준점 = 곧은 선 · 길찾기 안 함
|
|
assert (127.72, 127.5) not in fake.asked
|
|
assert shots["r2"][0] == [[[35.45, 127.60], [35.4, 127.5]]] # 길찾기 실패 = 곧은 선
|
|
assert (tmp_path / "B08_Quantity/milestone/auto/r1.png").read_bytes() == PNG
|
|
|
|
|
|
def test_바뀐_경로만_다시(tmp_path, monkeypatch):
|
|
fake, box = _setup(tmp_path, monkeypatch)
|
|
_save(tmp_path, DATA)
|
|
fake.shots.clear()
|
|
ms.refresh_images(tmp_path)
|
|
assert fake.shots == [] # 안 바뀜
|
|
moved = {**DATA, "bases": [BASES[0], {**BASES[1], "lat": 35.55}]}
|
|
_save(tmp_path, moved)
|
|
assert sorted(s[0] for s in fake.shots) == ["r1", "r4"] # b2 를 지나는 경로만
|
|
fake.shots.clear()
|
|
box["start"] = [127.4, 35.3] # 시점 옮김(노선 수정) = 모두
|
|
ms.refresh_images(tmp_path)
|
|
assert sorted(s[0] for s in fake.shots) == ["r1", "r2", "r4"]
|
|
fake.shots.clear()
|
|
(tmp_path / "B08_Quantity/milestone/auto/r2.png").unlink() # 그림 없음 = 다시
|
|
ms.refresh_images(tmp_path)
|
|
assert [s[0] for s in fake.shots] == ["r2"]
|
|
|
|
|
|
def test_길찾기_그림_뒤엔_다음_저장에_전체로(tmp_path, monkeypatch):
|
|
fake, _ = _setup(tmp_path, monkeypatch)
|
|
|
|
async def root(project_id):
|
|
return tmp_path
|
|
|
|
monkeypatch.setattr(ms, "_project_root", root)
|
|
monkeypatch.delenv("PYTEST_CURRENT_TEST") # [저장] 뒤 캡처 켬(시험 중엔 꺼 둠)
|
|
app = FastAPI()
|
|
app.include_router(ms.router)
|
|
client = TestClient(app)
|
|
base = f"/api/projects/{uuid4()}/quantity/milestone"
|
|
assert client.put(base, json=DATA).status_code == 200 # 답 뒤 캡처
|
|
assert sorted(s[0] for s in fake.shots) == ["r1", "r2", "r4"]
|
|
fake.shots.clear()
|
|
part = {"lines": [[[35.70, 127.95], [35.68, 127.91]]], "marks": []}
|
|
assert client.post(base + "/image/auto", params={"route": "r1"}, json=part).status_code == 200
|
|
assert [s[0] for s in fake.shots] == ["r1"] # 화면 길찾기 그림(거래처 구간만)
|
|
fake.shots.clear()
|
|
ms.refresh_images(tmp_path)
|
|
assert [(s[0], len(s[2])) for s in fake.shots] == [("r1", 4)] # 전체 경로로 다시
|