belfastbuild-backend / backend /tests /test_api_contract.py
pritamdeka
commit
21f359b
Raw
History Blame Contribute Delete
3.24 kB
"""End-to-end API contract tests.
Spec section 6.1: happy path, postcode regex (422), description length (422).
"""
from __future__ import annotations
from pathlib import Path
import pytest
from fastapi.testclient import TestClient
from app.config import PDF_DIR
from app.ingestion import SOURCE_REGISTRY
from app.main import app
from app.vector_store import get_vector_store
@pytest.fixture(scope="module", autouse=True)
def _bootstrap_corpus(tmp_path_factory) -> None:
"""Copy the on-disk PDFs into a temp dir and point the vector store there."""
base = tmp_path_factory.mktemp("belfastbuild-api")
target = base / "pdfs"
target.mkdir(parents=True, exist_ok=True)
for entry in SOURCE_REGISTRY:
src = PDF_DIR / entry["filename"]
if src.exists():
(target / entry["filename"]).write_bytes(src.read_bytes())
import shutil
chroma_dir = base / "chroma"
chroma_dir.mkdir(parents=True, exist_ok=True)
from app.config import CHROMA_DIR
from app.ingestion import PDF_DIR as INGEST_PDF_DIR
from app import vector_store
# Override module-level paths so the live vector store points at temp dirs.
vector_store.PDF_DIR = INGEST_PDF_DIR
vector_store.CHROMA_DIR = chroma_dir # type: ignore[misc]
# Reset the cached store and trigger a real re-index.
vector_store.get_vector_store.cache_clear()
store = vector_store.get_vector_store()
store.index_corpus(force_reindex=True)
client = TestClient(app)
def test_health_endpoint() -> None:
response = client.get("/api/health")
assert response.status_code == 200
body = response.json()
assert body["status"] == "ok"
assert body["indexed"] is True
assert body["ready_for_screening"] is True
assert "spatial_lookup_enabled" in body
assert "corpus_fingerprint" in body
def test_happy_path_returns_200_with_integer_score() -> None:
payload = {
"postcode": "BT9 7AG",
"description": "Single-storey rear extension to a semi-detached dwelling with off-street parking.",
}
response = client.post("/api/screen", json=payload)
assert response.status_code == 200, response.text
body = response.json()
assert isinstance(body["compliance_percentage"], int)
assert 0 <= body["compliance_percentage"] <= 100
assert isinstance(body["policy_conflicts"], list)
assert isinstance(body["source_citations"], list)
assert "remediation_markdown" in body and body["remediation_markdown"]
def test_non_ni_postcode_rejected_with_422() -> None:
response = client.post(
"/api/screen",
json={"postcode": "SW1A 1AA", "description": "Single-storey rear extension with off-street parking."},
)
assert response.status_code == 422
def test_short_description_rejected_with_422() -> None:
response = client.post(
"/api/screen",
json={"postcode": "BT9 7AG", "description": "tiny"},
)
assert response.status_code == 422
def test_malformed_postcode_rejected_with_422() -> None:
response = client.post(
"/api/screen",
json={"postcode": "BLAH", "description": "Single-storey rear extension to a residential property."},
)
assert response.status_code == 422