""" Smoke tests for the FastAPI layer. These deliberately do NOT require a live Postgres/pgvector or Ollama instance - they assert that every endpoint is reachable and degrades gracefully (returns a sensible JSON error/empty-result rather than a 500/crash) when those dependencies are unavailable, which is the situation on a fresh checkout before `.env` + the database are set up. For tests that exercise real retrieval/generation against a live database and Ollama, see `tests/test_rag_integration.py` (skipped automatically unless `RUN_INTEGRATION_TESTS=1` is set - see that file). """ from __future__ import annotations # Environment setup (settings that must exist before app.main is imported, # including the auth secrets) lives in tests/conftest.py, which pytest loads # first. The `client`, `admin_headers` and `user_headers` fixtures come from # there too. def test_root(client) -> None: resp = client.get("/") assert resp.status_code == 200 if "text/html" in resp.headers.get("content-type", ""): assert " None: resp = client.get("/api/health") assert resp.status_code == 200 body = resp.json() assert body["status"] in {"ok", "degraded"} assert isinstance(body["database"], bool) assert isinstance(body["ollama"], bool) def test_brands_returns_empty_list_without_database(client) -> None: resp = client.get("/api/brands") assert resp.status_code == 200 assert resp.json() == {"brands": []} def test_brand_products_returns_empty_without_database(client) -> None: resp = client.get("/api/brands/Parle/products") assert resp.status_code == 200 body = resp.json() assert body["brand"] == "Parle" assert body["products"] == [] assert body["total"] == 0 def test_product_detail_404_when_missing(client) -> None: resp = client.get("/api/brands/Parle/products/does-not-exist") assert resp.status_code == 404 # These now sign in first. The guard runs before body validation, so an # unauthenticated call gets 401 and never reaches the schema check these # assertions are actually about. tests/test_auth.py covers the guards # themselves. def test_chat_validates_empty_query(client, user_headers) -> None: resp = client.post("/api/chat", json={"query": ""}, headers=user_headers) assert resp.status_code == 422 # min_length=1 violated def test_chat_rejects_too_many_top_k(client, user_headers) -> None: resp = client.post("/api/chat", json={"query": "snacks", "top_k": 999}, headers=user_headers) assert resp.status_code == 422 # le=15 violated def test_catalog_generate_returns_job_id(client, admin_headers) -> None: resp = client.post( "/api/catalog/generate", json={"brand": "TestBrand", "max_products": 1}, headers=admin_headers, ) assert resp.status_code == 202 body = resp.json() assert body["brand"] == "TestBrand" assert body["status"] in {"pending", "running", "done", "failed"} def test_openapi_schema_lists_all_routers(client) -> None: resp = client.get("/openapi.json") assert resp.status_code == 200 paths = resp.json()["paths"] for expected in ("/api/health", "/api/brands", "/api/search", "/api/chat", "/api/catalog/generate"): assert expected in paths, f"missing route: {expected}"