Files
Aislo/resources/tester/test_256_1_b08_milestone_place_image.py
T

163 lines
6.9 KiB
Python

"""256-1 B08 거리이정표 장소 그림 영구 저장(사용자 162-6 · 7).
캡처는 가짜(make_place_image 자리)로:
① 장소 그림 페이지 = 480×320 · 고정 줌 · 경로 맞춤 안 함
② [저장] 뒤 좌표 있는 상자(거래처 = 경로 id · 기준점 = b1)만 캡처 → place/<id>.png ·
GET 답 place_images · place_times
③ 다시 [저장] = 바뀐 상자 · 그림 없는 상자만 · 실패 = 다음에 다시 · 지문에 좌표 원문 없음
④ 지운 상자 그림 = [저장] 때 지움 · 화면 캡처 길(place-image) · 그림 파일 길(file · 경로 넘기 막음)
"""
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_Milestone_Capture as cap # noqa: E402
from B08_Quantity import B08_Quantity_Router_Milestone as ms # noqa: E402
PNG = b"\x89PNG\r\n\x1a\nfake-place"
DATA = {
"base": {"enabled": True, "name": "거창IC", "lon": 127.91, "lat": 35.68},
"routes": [
{"id": "r1", "name": "거창레미콘", "lon": 127.9095, "lat": 35.6866},
{"id": "r2", "name": "좌표 없음"},
],
}
class Fake:
"""가짜 캡처 — 부른 상자 기록 · ok=False 면 실패."""
def __init__(self):
self.calls = []
self.ok = True
def __call__(self, out_dir, place_id, mark):
self.calls.append((place_id, mark["name"], mark["lon"], mark["lat"]))
if not self.ok:
return {"saved": False, "reason": "장소 그림 자동 캡처 실패 — 시험"}
out_dir.mkdir(parents=True, exist_ok=True)
(out_dir / f"{place_id}.png").write_bytes(PNG)
return {"saved": True, "reason": None}
def _save(root, data):
return ms.save_data(root, ms.Milestone.model_validate(data))
def _fake(monkeypatch):
fake = Fake()
monkeypatch.setattr(ms, "make_place_image", fake)
monkeypatch.setattr(ms, "route_image", lambda *a: {"saved": False}) # 경로 그림 = 256-2 시험
return fake
def test_장소_그림_페이지_작은_지도():
html = cap.page_html(
"k", [], [{"name": "거창레미콘", "lon": 127.9, "lat": 35.6}], cap.PLACE_VIEW
)
assert "width:480px;height:320px" in html and "level: 4" in html
assert "if (false)" in html # 경로 맞춤 안 함 — 표지 가운데 · 고정 줌
assert "if (true)" in cap.page_html("k", [], []) # 경로 그림은 그대로
def test_저장_뒤_좌표_있는_상자만_캡처(tmp_path, monkeypatch):
fake = _fake(monkeypatch)
_save(tmp_path, DATA)
ms.refresh_images(tmp_path)
assert sorted(c[0] for c in fake.calls) == ["b1", "r1"]
got = ms.payload(tmp_path, ms.read_data(tmp_path))
assert got["place_images"] == {"b1": "place/b1.png", "r1": "place/r1.png"}
assert set(got["place_times"]) == {"b1", "r1"}
assert all(isinstance(t, int) and t > 0 for t in got["place_times"].values())
assert (tmp_path / "B08_Quantity/milestone/place/r1.png").read_bytes() == PNG
prints = (tmp_path / "B08_Quantity/milestone/.prints.json").read_text(encoding="utf-8")
assert "127.9095" not in prints and "35.6866" not in prints # 해시만
def test_바뀐_상자_그림_없는_상자만_다시(tmp_path, monkeypatch):
fake = _fake(monkeypatch)
_save(tmp_path, DATA)
ms.refresh_images(tmp_path)
fake.calls.clear()
ms.refresh_images(tmp_path)
assert fake.calls == [] # 안 바뀜 = 캡처 0
moved = {**DATA, "routes": [{**DATA["routes"][0], "lat": 35.7}, DATA["routes"][1]]}
_save(tmp_path, moved)
ms.refresh_images(tmp_path)
assert [c[0] for c in fake.calls] == ["r1"]
fake.calls.clear()
(tmp_path / "B08_Quantity/milestone/place/b1.png").unlink()
ms.refresh_images(tmp_path)
assert [c[0] for c in fake.calls] == ["b1"]
# 실패 = 지문 안 적음 → 다음에 다시
fake.calls.clear()
fake.ok = False
_save(tmp_path, {**moved, "base": {**DATA["base"], "lon": 127.95}})
ms.refresh_images(tmp_path)
ms.refresh_images(tmp_path)
assert [c[0] for c in fake.calls] == ["b1", "b1"]
def test_지운_상자_그림은_저장_때_지움(tmp_path, monkeypatch):
_fake(monkeypatch)
_save(tmp_path, DATA)
ms.refresh_images(tmp_path)
got = _save(tmp_path, {"routes": [DATA["routes"][1]]}) # r1 · 기준점 좌표 지움
assert got["place_images"] == {} and got["place_times"] == {}
assert not list((tmp_path / "B08_Quantity/milestone/place").iterdir())
def test_화면_캡처_길_그림_파일_길(tmp_path, monkeypatch):
fake = _fake(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"
# [저장] = 답 뒤 캡처(BackgroundTasks — TestClient 는 답 전에 끝냄)
assert client.put(base, json=DATA).status_code == 200
assert sorted(c[0] for c in fake.calls) == ["b1", "r1"]
assert client.get(base).json()["place_images"]["r1"] == "place/r1.png"
# 등록 · 끌기 뒤 화면이 부름
fake.calls.clear()
mark = {"name": "새 거래처", "lon": 127.8, "lat": 35.5}
res = client.post(base + "/place-image", params={"id": "r9"}, json=mark).json()
assert res["name"] == "place/r9.png" and res["reason"] is None and res["time"] > 0
assert fake.calls == [("r9", "새 거래처", 127.8, 35.5)]
fake.ok = False
bad = client.post(base + "/place-image", params={"id": "r8"}, json=mark).json()
assert bad == {"name": None, "time": None, "reason": "장소 그림 자동 캡처 실패 — 시험"}
assert client.post(base + "/place-image", params={"id": "../x"}, json=mark).status_code == 400
# 그림 파일 길 — 하위 폴더 둘 · 경로 넘기 막음
assert client.get(base + "/file", params={"name": "place/r1.png"}).content == PNG
assert client.get(base + "/file", params={"name": "place/zz.png"}).status_code == 404
for name in ("../milestone.json", "place/../x.png", "x/r1.png", "place/r1.json", "/etc/x.png"):
assert client.get(base + "/file", params={"name": name}).status_code == 422, name
def test_여러_기준점_상자_id(tmp_path, monkeypatch):
fake = _fake(monkeypatch)
bases = [
{"id": "b1", "name": "거창IC", "lon": 127.91, "lat": 35.68, "to": "b2"},
{"id": "b2", "name": "함양JC", "lon": 127.72, "lat": 35.52},
{"id": "b3", "name": "좌표 없음"},
]
_save(tmp_path, {"bases": bases, "routes": [{**DATA["routes"][0], "to": "b1"}]})
ms.refresh_images(tmp_path)
assert sorted(c[0] for c in fake.calls) == ["b1", "b2", "r1"]
got = _save(tmp_path, {"bases": bases[1:], "routes": []}) # b1 · r1 지움
assert got["place_images"] == {"b2": "place/b2.png"}