rmi-backend/tests/contract/test_openapi_contract.py
opencode c762564d40 style(rmi-backend): complete lint cleanup — 1175→0 ruff errors
- Fix 71 invalid-syntax files (class-body newline-broken assignments)
- Add from/None chain to 307 B904 raise-without-from sites
- Add B008 ignore to ruff.toml (already in pyproject.toml)
- Noqa F401 on __init__.py re-exports (137 sites)
- Noqa E402 on deferred imports (63 sites)
- Bulk-add stdlib/FastAPI/project imports for F821 (127 sites)
- Replace ×→x, –→-, …→... in docstrings (4093 chars)
- Manual refactor of 5 SIM103/SIM116 patterns

Tests: 791 passed (66 deselected due to pre-existing Redis issues in test_rag.py)
Co-authored-by: opencode <opencode@rugmunch.io>
2026-07-06 15:43:20 +02:00

94 lines
2.9 KiB
Python

"""Contract tests - verify API responses match OpenAPI schema.
Uses schemathesis to fuzz test that every endpoint returns responses
that conform to the declared schema. Catches type mismatches, missing
required fields, and incorrect status codes.
T21: Run with `pytest tests/contract/ -x`
"""
import pytest
# Skip if server is not running or schemathesis not installed
schemathesis = pytest.importorskip("schemathesis")
SCHEMA_URL = "http://localhost:8000/openapi.json"
@pytest.fixture(scope="module")
def schema():
"""Load the OpenAPI schema from the running server."""
try:
return schemathesis.from_uri(SCHEMA_URL)
except Exception:
pytest.skip("Backend not running at localhost:8000")
@pytest.fixture(scope="module")
def api_state(schema):
"""Set up Hypothesis strategies for property-based testing."""
return schema.as_state_machine()
def test_openapi_schema_loads():
"""Verify the OpenAPI schema is valid and loadable."""
import httpx
resp = httpx.get(SCHEMA_URL)
assert resp.status_code == 200, f"OpenAPI schema not available: {resp.status_code}"
data = resp.json()
assert "paths" in data
assert "components" in data
assert len(data["paths"]) > 10, "Expected at least 10 endpoints"
def test_health_endpoint_contract():
"""Health endpoint must return 200 with correct schema."""
import httpx
resp = httpx.get("http://localhost:8000/health")
assert resp.status_code == 200
data = resp.json()
assert "status" in data
assert "stores" in data
assert data["status"] in ("healthy", "degraded", "unhealthy")
def test_liveness_contract():
"""Liveness endpoint must return {status: 'alive'}."""
import httpx
resp = httpx.get("http://localhost:8000/live")
assert resp.status_code == 200
data = resp.json()
assert data["status"] == "alive"
def test_readiness_contract():
"""Readiness endpoint must return {status: 'ready'|'not_ready', checks: {...}}."""
import httpx
resp = httpx.get("http://localhost:8000/ready")
assert resp.status_code == 200
data = resp.json()
assert data["status"] in ("ready", "not_ready")
assert "checks" in data
assert isinstance(data["checks"], dict)
def test_metrics_endpoint_serves_prometheus():
"""/metrics must return Prometheus exposition format."""
import httpx
resp = httpx.get("http://localhost:8000/metrics")
assert resp.status_code == 200
assert "HELP" in resp.text or "#" in resp.text, "Not Prometheus format"
def test_no_endpoint_returns_500_on_valid_input():
"""Valid requests to known endpoints should not return 500."""
import httpx
endpoints = [
("GET", "/health"),
("GET", "/live"),
("GET", "/ready"),
("GET", "/metrics"),
]
for method, path in endpoints:
resp = httpx.request(method, f"http://localhost:8000{path}")
assert resp.status_code != 500, f"{method} {path} returned 500"