Spaces:
Running
Running
| """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 | |
| 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 | |