Spaces:
Sleeping
Sleeping
| """ | |
| 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 | |