110 lines
4.1 KiB
Python
110 lines
4.1 KiB
Python
"""
|
|
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 "<html" in resp.text.lower()
|
|
else:
|
|
assert "service" in resp.json()
|
|
|
|
|
|
def test_health_degrades_gracefully_without_dependencies(client) -> 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/suggest",
|
|
"/api/chat", "/api/catalog/generate"):
|
|
assert expected in paths, f"missing route: {expected}"
|
|
|
|
|
|
def test_suggest_works_without_a_database(client) -> None:
|
|
"""Autocomplete must degrade, not 500, when the catalog is unreachable.
|
|
|
|
This suite runs with no reachable database, so this pins that the search
|
|
box keeps suggesting from the static brand table instead of erroring - a
|
|
failing suggest call must never block someone from typing and searching.
|
|
"""
|
|
resp = client.get("/api/suggest", params={"q": "cavin"})
|
|
assert resp.status_code == 200
|
|
labels = [s["label"] for s in resp.json()["suggestions"]]
|
|
assert "Cavinkare" in labels
|
|
|
|
|
|
def test_suggest_requires_a_query(client) -> None:
|
|
assert client.get("/api/suggest").status_code == 422
|