hasari-api / services /backend /tests /test_api.py
erdoganpeker's picture
v0.3.0 — multimodal vehicle damage MVP
e327f0d
"""
Backend API testleri.
Coverage:
- Health endpoint
- Version endpoint
- Auth: dev mode + (mocked) prod mode
- Inspect sync (mock ML)
- CORS preflight
- 404/403 davranisi
"""
from __future__ import annotations
import io
import pytest
from fastapi.testclient import TestClient
# ---------------- Health & version ----------------
def test_health_ok(client: TestClient):
r = client.get("/health")
assert r.status_code == 200
body = r.json()
assert body["status"] == "ok"
assert body["ml_loaded"] is True
assert "timestamp" in body
assert "version" in body
def test_version_endpoint(client: TestClient):
r = client.get("/api/v1/version")
assert r.status_code == 200
body = r.json()
assert "version" in body
assert "git_sha" in body
assert "build_time" in body
assert "environment" in body
# ---------------- Auth ----------------
def test_dev_mode_allows_no_api_key(client: TestClient):
"""API_KEYS bos => dev mode => key olmadan calismali."""
r = client.get("/api/v1/inspect", params={"page": 1, "page_size": 10})
# 200: list bos olabilir ama auth gecti
assert r.status_code == 200
body = r.json()
assert "items" in body
assert body["page"] == 1
def test_prod_mode_requires_api_key(client: TestClient, monkeypatch):
"""API_KEYS dolu => header olmadan 401."""
from config import settings
monkeypatch.setattr(settings, "api_keys", ["valid-key-123"])
monkeypatch.setattr(settings, "environment", "production")
r = client.get("/api/v1/inspect")
assert r.status_code == 401
assert "X-API-Key" in r.json()["detail"]
def test_prod_mode_invalid_api_key(client: TestClient, monkeypatch):
from config import settings
monkeypatch.setattr(settings, "api_keys", ["valid-key-123"])
monkeypatch.setattr(settings, "environment", "production")
r = client.get("/api/v1/inspect", headers={"X-API-Key": "wrong"})
assert r.status_code == 403
def test_prod_mode_valid_api_key(client: TestClient, monkeypatch):
from config import settings
monkeypatch.setattr(settings, "api_keys", ["valid-key-123"])
monkeypatch.setattr(settings, "environment", "production")
r = client.get("/api/v1/inspect", headers={"X-API-Key": "valid-key-123"})
assert r.status_code == 200
# ---------------- CORS ----------------
def test_cors_preflight_localhost_web(client: TestClient):
"""Next.js dev origin'inden preflight gecmeli."""
r = client.options(
"/api/v1/inspect",
headers={
"Origin": "http://localhost:3000",
"Access-Control-Request-Method": "POST",
"Access-Control-Request-Headers": "x-api-key,content-type",
},
)
assert r.status_code in (200, 204)
assert r.headers.get("access-control-allow-origin") == "http://localhost:3000"
assert "POST" in r.headers.get("access-control-allow-methods", "")
def test_cors_preflight_tauri(client: TestClient):
"""Tauri desktop origin'i kabul edilmeli."""
r = client.options(
"/api/v1/inspect",
headers={
"Origin": "http://tauri.localhost",
"Access-Control-Request-Method": "POST",
"Access-Control-Request-Headers": "x-api-key",
},
)
assert r.status_code in (200, 204)
assert r.headers.get("access-control-allow-origin") == "http://tauri.localhost"
def test_cors_preflight_vercel_regex(client: TestClient):
"""*.vercel.app origin'i regex ile kabul edilmeli."""
r = client.options(
"/api/v1/inspect",
headers={
"Origin": "https://my-app-abc123.vercel.app",
"Access-Control-Request-Method": "GET",
"Access-Control-Request-Headers": "x-api-key",
},
)
assert r.status_code in (200, 204)
assert "vercel.app" in (r.headers.get("access-control-allow-origin") or "")
# ---------------- Inspect ----------------
def _png_bytes() -> bytes:
"""Minimal gecerli 1x1 PNG."""
import base64
return base64.b64decode(
b"iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII="
)
def test_sync_single_inspection(client: TestClient):
"""Tek goruntu sync inspection — mock ML donduruyor."""
img = _png_bytes()
r = client.post(
"/api/v1/inspect/sync",
files={"file": ("test.png", img, "image/png")},
)
assert r.status_code == 200, r.text
body = r.json()
assert "inspection_id" in body
assert "result" in body
assert body["result"]["summary"]["total_parts_inspected"] == 0
def test_sync_rejects_non_image(client: TestClient):
"""Image olmayan MIME tipi reddedilmeli."""
r = client.post(
"/api/v1/inspect/sync",
files={"file": ("test.txt", b"not an image", "text/plain")},
)
assert r.status_code == 400
def test_inspect_404_for_missing(client: TestClient):
r = client.get("/api/v1/inspect/00000000-0000-0000-0000-000000000000")
assert r.status_code == 404
assert "bulunamadi" in r.json()["detail"].lower()
def test_inspect_async_mode_returns_202(client: TestClient):
img = _png_bytes()
r = client.post(
"/api/v1/inspect?mode=async",
files=[("files", ("test1.png", img, "image/png"))],
)
assert r.status_code == 202, r.text
body = r.json()
assert body["status"] == "queued"
assert body["status_url"].startswith("/api/v1/inspect/")
assert "X-Inspection-Id" in r.headers