"""Tests for JSON API upload and job payloads.""" from __future__ import annotations from pathlib import Path import pytest from app import config, pipeline from app.main import app from fastapi.testclient import TestClient from PIL import Image @pytest.fixture def client(data_dir: Path) -> TestClient: return TestClient(app) @pytest.fixture def upload_token(monkeypatch: pytest.MonkeyPatch) -> str: token = "test-upload-token" monkeypatch.setattr(config, "UPLOAD_TOKEN", token) return token def _jpeg_bytes(size: tuple[int, int] = (80, 60)) -> bytes: from io import BytesIO buf = BytesIO() Image.new("RGB", size, color=(12, 34, 56)).save(buf, format="JPEG", quality=85) return buf.getvalue() def test_health_reports_upload_disabled(client: TestClient, monkeypatch: pytest.MonkeyPatch) -> None: monkeypatch.setattr(config, "UPLOAD_TOKEN", "") response = client.get("/api/health") assert response.status_code == 200 assert response.json() == {"ok": True, "upload": False} def test_upload_disabled_without_token(client: TestClient, monkeypatch: pytest.MonkeyPatch) -> None: monkeypatch.setattr(config, "UPLOAD_TOKEN", "") response = client.post( "/api/jobs", files={"file": ("party.jpg", _jpeg_bytes(), "image/jpeg")}, ) assert response.status_code == 503 def test_upload_rejects_bad_token(client: TestClient, upload_token: str) -> None: response = client.post( "/api/jobs", files={"file": ("party.jpg", _jpeg_bytes(), "image/jpeg")}, headers={"Authorization": "Bearer nope"}, ) assert response.status_code == 401 def test_upload_accepts_bearer_and_queues(client: TestClient, upload_token: str, data_dir: Path) -> None: response = client.post( "/api/jobs", files={"file": ("party.jpg", _jpeg_bytes(), "image/jpeg")}, headers={"Authorization": f"Bearer {upload_token}"}, ) assert response.status_code == 202 body = response.json() assert body["status"] == "queued" job_id = body["job_id"] assert job_id.endswith("_party") inbox = list(config.INBOX_DIR.glob("*_party.jpg")) assert len(inbox) == 1 assert not inbox[0].name.startswith(".") meta = pipeline.read_meta(job_id) assert meta is not None assert meta["status"] == "queued" def test_upload_accepts_x_upload_token_header(client: TestClient, upload_token: str) -> None: response = client.post( "/api/jobs", files={"file": ("cam.png", _jpeg_bytes(), "image/png")}, headers={"X-Upload-Token": upload_token}, ) assert response.status_code == 202 assert response.json()["status"] == "queued" def test_upload_rejects_empty_file(client: TestClient, upload_token: str) -> None: response = client.post( "/api/jobs", files={"file": ("empty.jpg", b"", "image/jpeg")}, headers={"Authorization": f"Bearer {upload_token}"}, ) assert response.status_code == 400 def test_api_job_detail_and_list(client: TestClient, data_dir: Path) -> None: stem = "photo01" paths = pipeline.job_paths(stem) paths.variants.mkdir(parents=True, exist_ok=True) png = paths.variants / f"{stem}_v1.png" Image.new("RGB", (32, 32), color=(1, 2, 3)).save(png, format="PNG") pipeline.write_status( paths, "processing", stage="variant", progress={"current": 2, "total": 3}, created_at="2026-01-01T00:00:00", ) pipeline.write_manifest( paths, { "variants": [{"id": "v1", "file": png.name}], "created_at": "2026-01-01T00:00:00", }, ) listed = client.get("/api/jobs") assert listed.status_code == 200 payload = listed.json() assert payload["active"] is True assert payload["jobs"][0]["job_id"] == stem assert payload["jobs"][0]["status_label"] == "Läuft" assert payload["jobs"][0]["stage_label"] == "Variante 2/3" detail = client.get(f"/api/jobs/{stem}") assert detail.status_code == 200 body = detail.json() assert body["has_rembg"] is False assert body["variants"][0]["url"].endswith(f"/jobs/{stem}/files/{png.name}") assert body["progress"] == {"current": 2, "total": 3} def test_api_job_missing(client: TestClient) -> None: assert client.get("/api/jobs/does-not-exist").status_code == 404