File size: 7,109 Bytes
d2d1903 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 | """
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()
|