Spaces:
Running
Running
| """Smoke tests for the FastAPI surface area. | |
| Run: | |
| pytest tests/ -v | |
| External services stays behind affordances: | |
| * `pytest -m "not supabase and not nvidia"` runs offline smoke only. | |
| """ | |
| from __future__ import annotations | |
| import io | |
| import pytest | |
| pytestmark = pytest.mark.asyncio | |
| # ---- /health & / ---- | |
| def test_health(fastapi_client): | |
| r = fastapi_client.get("/health") | |
| assert r.status_code == 200, r.text | |
| body = r.json() | |
| assert body["status"] == "ok" | |
| assert body["service"] == "virtual-foreman-backend" | |
| def test_root_lists_endpoints(fastapi_client): | |
| r = fastapi_client.get("/") | |
| assert r.status_code == 200 | |
| body = r.json() | |
| assert "POST /projects" in body["endpoints"] | |
| assert "POST /work/infer" in body["endpoints"] | |
| assert "POST /work/blueprint" in body["endpoints"] | |
| assert "POST /documents/render" in body["endpoints"] | |
| assert "GET /inference/history" in body["endpoints"] | |
| def test_request_id_header_echoed(fastapi_client): | |
| r = fastapi_client.get("/health", headers={"X-Request-ID": "rid-abc-123"}) | |
| assert r.headers.get("X-Request-ID") == "rid-abc-123" | |
| def test_request_id_autogenerated_when_missing(fastapi_client): | |
| r = fastapi_client.get("/health") | |
| rid = r.headers.get("X-Request-ID") | |
| assert rid is not None and len(rid) > 0 | |
| # ---- /documents/templates ---- | |
| def test_documents_templates(fastapi_client): | |
| r = fastapi_client.get("/documents/templates") | |
| assert r.status_code == 200 | |
| body = r.json() | |
| assert "supported" in body | |
| assert isinstance(body["supported"], list) | |
| assert "act_hidden_works" in body["supported"] | |
| assert "general_work_log" in body["supported"] | |
| # ---- /work/infer ---- | |
| async def test_work_infer_no_payload_returns_422(fastapi_client): | |
| r = fastapi_client.post("/work/infer", json={"wrong": "shape"}) | |
| assert r.status_code == 422 | |
| async def test_work_infer_short_text_returns_422(fastapi_client): | |
| r = fastapi_client.post( | |
| "/work/infer", | |
| json={ | |
| "raw_text": "ab", # too short | |
| "date_from": "2026-06-22", | |
| "date_to": "2026-06-22", | |
| }, | |
| ) | |
| assert r.status_code == 422 | |
| # ---- /work/blueprint (offline-friendly path) ---- | |
| async def test_blueprint_reject_invalid_mime(fastapi_client): | |
| fake = (b"not really a video", "test.gif", "image/gif") | |
| r = fastapi_client.post( | |
| "/work/blueprint", | |
| files={"file": (fake[1], io.BytesIO(fake[0]), fake[2])}, | |
| ) | |
| assert r.status_code == 415 | |
| assert "image/png" in r.json()["detail"] | |
| async def test_blueprint_reject_empty(fastapi_client): | |
| r = fastapi_client.post( | |
| "/work/blueprint", | |
| files={"file": ("empty.png", io.BytesIO(b""), "image/png")}, | |
| ) | |
| # FastAPI's UploadFile may read empty bytes — endpoint catches and | |
| # returns 400 "empty upload". | |
| assert r.status_code in (400, 422) | |
| detail = (r.json().get("detail") or "").lower() | |
| assert "empty" in detail or "required" in detail | |
| # ---- /projects validation ---- | |
| async def test_projects_create_missing_fields_returns_422(fastapi_client): | |
| r = fastapi_client.post("/projects", json={"contract_no": "12-2026/BN"}) | |
| # contract_no isn't enough — many required fields missing. | |
| assert r.status_code == 422 | |
| async def test_projects_get_unknown_id_returns_404_or_503(fastapi_client): | |
| """Off the offline path this either 404s (real Supabase), 503 (Supabase | |
| unreachable), 500 (broken client) — never 200 because the row isn't | |
| real. | |
| """ | |
| r = fastapi_client.get( | |
| "/projects/00000000-0000-0000-0000-000000000000" | |
| ) | |
| assert r.status_code in (404, 503) | |