hasari-api / services /backend /tests /test_visualization.py
erdoganpeker's picture
v0.3.0 — multimodal vehicle damage MVP
e327f0d
"""
Visualization endpoint testleri.
GET /api/v1/inspect/{id}/visualization/{annotated|parts|damages}
Beklenen davranis:
- completed inspection icin result.visualization_urls var ise -> 302 redirect
- yoksa storage.get_presigned_url fallback'inden URL uretir veya 404
Bu test'te:
1. Sync inspection olusturup, sonra result icine visualization_urls enjekte ederiz
2. GET endpoint'i 302 doner ve Location header'i set eder
3. Olmayan tipler icin 404 doner (mocked get_presigned_url ile)
"""
from __future__ import annotations
import httpx
import pytest
def _bearer(token: str) -> dict[str, str]:
return {"Authorization": f"Bearer {token}"}
async def _setup_completed_with_viz(
async_client: httpx.AsyncClient,
png_bytes: bytes,
viz_url_map: dict | None = None,
) -> tuple[str, str]:
"""Sync inspection olustur ve result.visualization_urls'i guncelle.
Returns: (inspection_id, access_token)
"""
reg = await async_client.post(
"/auth/register",
json={"email": "viz@test.example.com", "password": "strong-pass-1234"},
)
token = reg.json()["access_token"]
files = {"file": ("a.png", png_bytes, "image/png")}
r = await async_client.post(
"/api/v1/inspect/sync",
files=files,
headers=_bearer(token),
)
assert r.status_code == 200
inspection_id = r.json()["inspection_id"]
if viz_url_map is not None:
# Mevcut result'i al, visualization_urls ekle
from main import get_db, update_inspection
existing = get_db().get(inspection_id)
result = dict(existing.get("result") or {})
result["visualization_urls"] = viz_url_map
update_inspection(inspection_id, result=result)
return inspection_id, token
async def test_visualization_redirects_302_when_url_exists(
async_client: httpx.AsyncClient, png_bytes: bytes,
reset_user_store, reset_inspection_store
):
viz_map = {
"annotated": "https://cdn.test.local/inspections/x/annotated.png?sig=abc",
"parts": "https://cdn.test.local/inspections/x/parts.png?sig=def",
"damages": "https://cdn.test.local/inspections/x/damages.png?sig=ghi",
}
inspection_id, token = await _setup_completed_with_viz(
async_client, png_bytes, viz_url_map=viz_map
)
for viz_type, expected_url in viz_map.items():
r = await async_client.get(
f"/api/v1/inspect/{inspection_id}/visualization/{viz_type}",
headers=_bearer(token),
follow_redirects=False,
)
assert r.status_code == 302, f"{viz_type}: {r.status_code} {r.text}"
assert r.headers.get("location") == expected_url
async def test_visualization_falls_back_to_presigned_when_no_urls(
async_client: httpx.AsyncClient, png_bytes: bytes, monkeypatch,
reset_user_store, reset_inspection_store
):
"""visualization_urls yoksa, storage.get_presigned_url cagrilir."""
fake_signed = "https://cdn.test.local/fallback/annotated.png?token=xyz"
def fake_get_presigned_url(key, expires_in=None):
return fake_signed
# main.py'deki import noktasini yamala
monkeypatch.setattr("main.get_presigned_url", fake_get_presigned_url)
inspection_id, token = await _setup_completed_with_viz(
async_client, png_bytes, viz_url_map=None
)
r = await async_client.get(
f"/api/v1/inspect/{inspection_id}/visualization/annotated",
headers=_bearer(token),
follow_redirects=False,
)
assert r.status_code == 302
assert r.headers.get("location") == fake_signed
async def test_visualization_returns_404_when_no_url_and_presign_fails(
async_client: httpx.AsyncClient, png_bytes: bytes, monkeypatch,
reset_user_store, reset_inspection_store
):
def fake_get_presigned_url(key, expires_in=None):
raise RuntimeError("S3 unreachable")
monkeypatch.setattr("main.get_presigned_url", fake_get_presigned_url)
inspection_id, token = await _setup_completed_with_viz(
async_client, png_bytes, viz_url_map=None
)
r = await async_client.get(
f"/api/v1/inspect/{inspection_id}/visualization/annotated",
headers=_bearer(token),
follow_redirects=False,
)
assert r.status_code == 404
async def test_visualization_invalid_type_returns_422(
async_client: httpx.AsyncClient, png_bytes: bytes,
reset_user_store, reset_inspection_store
):
inspection_id, token = await _setup_completed_with_viz(
async_client, png_bytes, viz_url_map=None
)
r = await async_client.get(
f"/api/v1/inspect/{inspection_id}/visualization/invalid_type_xyz",
headers=_bearer(token),
)
# Literal pattern check -> FastAPI 422
assert r.status_code == 422
async def test_visualization_404_when_inspection_missing(
async_client: httpx.AsyncClient,
reset_user_store, reset_inspection_store
):
reg = await async_client.post(
"/auth/register",
json={"email": "noinspec@test.example.com", "password": "strong-pass-1234"},
)
token = reg.json()["access_token"]
r = await async_client.get(
"/api/v1/inspect/00000000-0000-0000-0000-000000000000/visualization/annotated",
headers=_bearer(token),
follow_redirects=False,
)
assert r.status_code == 404