orgstate / tests /test_infra_api_openapi_examples.py
Legal-i's picture
Initial OrgState deploy via Stage 150 free-tier stack
d2d1903 verified
"""
Tests for Stage 86 β€” OpenAPI examples + SDK README.
Coverage:
1. SDK README exists at the right path
2. README mentions the 3 public classes + auth + transport
injection + Hebrew quickstart reference
3. OpenAPI schema includes the json_schema_extra examples
for each tightened body (7 schemas total)
4. Examples in the schema are well-formed (parse as JSON)
5. /docs and /openapi.json endpoints work end-to-end
"""
from pathlib import Path
import pytest
pytest.importorskip("fastapi")
pytest.importorskip("httpx")
from fastapi.testclient import TestClient
from infra import OrgStateService
from infra.api import create_app
from infra.api.schemas import (
ApiKeyMintBody,
CalibrateBody,
ObservationsIngestBody,
RunAnalysisBody,
ScheduleCreateBody,
TenantRegisterBody,
WebhookCreateBody,
)
SDK_README = Path(__file__).parent.parent / "orgstate_client" / "README.md"
# =========================================================
# SDK README β€” anchors
# =========================================================
def test_sdk_readme_exists():
assert SDK_README.exists(), \
f"SDK README missing at {SDK_README}"
def test_sdk_readme_mentions_public_classes():
text = SDK_README.read_text(encoding="utf-8")
for cls in ("Client", "AdminClient", "ClientError"):
assert cls in text, f"SDK README missing reference to {cls}"
def test_sdk_readme_documents_transport_injection():
"""transport injection is the key testability story β€”
the README must show how to plug FastAPI's TestClient in."""
text = SDK_README.read_text(encoding="utf-8")
assert "TestClient" in text
assert "transport=" in text
def test_sdk_readme_links_to_hebrew_quickstart_and_runbook():
"""The README is part of the customer-facing doc bundle.
Cross-link to the rest so navigation works in any tool."""
text = SDK_README.read_text(encoding="utf-8")
assert "CUSTOMER_QUICKSTART_HE.md" in text
assert "RUNBOOK.md" in text
def test_sdk_readme_includes_error_handling_example():
text = SDK_README.read_text(encoding="utf-8")
assert "ClientError" in text
assert "status_code" in text
assert "422" in text # Pydantic case from Stage 78
# =========================================================
# Pydantic schemas β€” json_schema_extra contains examples
# =========================================================
@pytest.mark.parametrize("model,expected_field", [
(TenantRegisterBody, "tenant_id"),
(CalibrateBody, "observations"),
(RunAnalysisBody, "observations"),
(ScheduleCreateBody, "connector_type"),
(WebhookCreateBody, "url"),
(ObservationsIngestBody, "entity_type"),
(ApiKeyMintBody, "role"),
])
def test_model_has_example_in_schema(model, expected_field):
"""Each tightened body should expose an `examples` array in
its JSON schema, and the example should mention the field
that defines its purpose."""
schema = model.model_json_schema()
examples = schema.get("examples", [])
assert len(examples) >= 1, \
f"{model.__name__} missing examples in schema"
example = examples[0]
assert expected_field in example, \
f"{model.__name__} example missing field {expected_field!r}"
def test_calibrate_example_observations_well_formed():
"""The observations example must have the structure that
ObservationModel requires (entity_id, day, values) β€”
otherwise a customer copy/pasting from /docs would get a
422 back."""
schema = CalibrateBody.model_json_schema()
example = schema["examples"][0]
obs = example["observations"]
assert isinstance(obs, list) and len(obs) >= 1
for o in obs:
assert "entity_id" in o
assert "day" in o
assert "values" in o and isinstance(o["values"], dict)
def test_example_actually_validates_against_the_model():
"""The strongest contract β€” the example schema MUST parse
against the model itself. Otherwise the /docs example is a
lie that breaks the first customer who copies it."""
schema = CalibrateBody.model_json_schema()
example = schema["examples"][0]
# constructing the model from the example raises if invalid
instance = CalibrateBody(**example)
assert instance.vertical == "logistics"
def test_webhook_example_validates():
example = WebhookCreateBody.model_json_schema()["examples"][0]
WebhookCreateBody(**example)
def test_api_key_example_validates():
example = ApiKeyMintBody.model_json_schema()["examples"][0]
inst = ApiKeyMintBody(**example)
assert inst.role == "operator"
def test_schedule_example_validates():
example = ScheduleCreateBody.model_json_schema()["examples"][0]
ScheduleCreateBody(**example)
# =========================================================
# /openapi.json and /docs endpoints work end-to-end
# =========================================================
def _bootstrap(tmp_path):
dbfile = str(tmp_path / "openapi.sqlite3")
svc = OrgStateService(dbfile)
try:
svc.register_tenant("acme", "ACME")
finally:
svc.close()
return dbfile
def test_openapi_json_endpoint_serves_spec(tmp_path):
dbfile = _bootstrap(tmp_path)
client = TestClient(create_app(dbfile))
r = client.get("/openapi.json")
assert r.status_code == 200
spec = r.json()
assert spec["info"]["title"] == "OrgState Engine API"
assert "paths" in spec
assert "components" in spec
def test_openapi_includes_pydantic_schemas(tmp_path):
"""The schemas section must reference the body models we
wired up in Stage 78 β€” verifies the schemas are in the
spec, not just in the route handlers."""
dbfile = _bootstrap(tmp_path)
client = TestClient(create_app(dbfile))
r = client.get("/openapi.json")
schemas = r.json()["components"]["schemas"]
for name in ("TenantRegisterBody", "WebhookCreateBody",
"CalibrateBody", "ScheduleCreateBody"):
assert name in schemas, f"OpenAPI missing schema {name}"
def test_openapi_schemas_carry_examples(tmp_path):
"""Stage 86 added json_schema_extra examples to the
7 main body schemas. End-to-end verification that the
examples make it into the served OpenAPI spec."""
dbfile = _bootstrap(tmp_path)
client = TestClient(create_app(dbfile))
schemas = client.get("/openapi.json").json()["components"]["schemas"]
for name in ("TenantRegisterBody", "WebhookCreateBody"):
assert "examples" in schemas[name] or \
"example" in schemas[name], \
f"{name} has no example in served OpenAPI spec"
def test_docs_html_endpoint_renders(tmp_path):
"""Swagger UI is auto-served by FastAPI at /docs. We don't
parse the HTML β€” just verify it 200s and isn't blocked by
our security_headers or rate_limit middlewares."""
dbfile = _bootstrap(tmp_path)
client = TestClient(create_app(dbfile))
r = client.get("/docs")
assert r.status_code == 200
assert "swagger" in r.text.lower() or "redoc" in r.text.lower() \
or "openapi" in r.text.lower()