GeoIntel release gates / Compile, test, contracts and builds (push) Successful in 1m49s
GeoIntel release gates / Python and npm vulnerability policy (push) Successful in 21s
GeoIntel release gates / Production AI image, SBOM and container scan (push) Successful in 5m39s
GeoIntel release gates / Deploy exact gated revision to Unraid (push) Failing after 58m43s
54 lines
1.6 KiB
Python
54 lines
1.6 KiB
Python
from __future__ import annotations
|
|
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
|
|
ROOT = Path(__file__).resolve().parents[2]
|
|
BACKEND = ROOT / "backend"
|
|
|
|
|
|
def test_no_untyped_fastapi_response_models_remain() -> None:
|
|
route_root = BACKEND / "app" / "api" / "routes"
|
|
route_sources = "\n".join(
|
|
path.read_text(encoding="utf-8")
|
|
for path in sorted(route_root.glob("*.py"))
|
|
)
|
|
|
|
assert "response_model=dict" not in route_sources
|
|
|
|
|
|
def test_every_json_success_response_has_a_concrete_canonical_schema() -> None:
|
|
sys.path.insert(0, str(ROOT))
|
|
sys.path.insert(0, str(BACKEND))
|
|
from app.main import create_app
|
|
|
|
from scripts.audit_api_contracts import (
|
|
ALLOWED_NON_ENVELOPE_ENDPOINTS,
|
|
_validate_response_contracts,
|
|
)
|
|
|
|
openapi = create_app().openapi()
|
|
assert _validate_response_contracts(openapi) == []
|
|
|
|
untyped_successes: set[tuple[str, str]] = set()
|
|
for path, path_item in openapi["paths"].items():
|
|
for method, operation in path_item.items():
|
|
if method.upper() not in {"GET", "POST", "PATCH", "DELETE"}:
|
|
continue
|
|
has_json_schema = any(
|
|
response.get("content", {})
|
|
.get("application/json", {})
|
|
.get("schema")
|
|
for code, response in operation.get("responses", {}).items()
|
|
if str(code).startswith("2")
|
|
)
|
|
if not has_json_schema:
|
|
untyped_successes.add((method.upper(), path))
|
|
|
|
assert untyped_successes == ALLOWED_NON_ENVELOPE_ENDPOINTS - {
|
|
("GET", "/health"),
|
|
("GET", "/health/live"),
|
|
("GET", "/health/ready"),
|
|
}
|