Spaces:
Sleeping
Sleeping
File size: 5,496 Bytes
e327f0d | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 | """
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
|