api: make the OpenAPI spec usable as documentation
ci / lint (push) Failing after 56s
ci / types (push) Has been skipped
ci / unit (push) Has been skipped
ci / integration (push) Has been skipped
ci / security (push) Has been skipped
ci / dockerfile (push) Has been skipped
ci / chart (push) Has been skipped
ci / image (api) (push) Has been skipped
ci / image (reconciler) (push) Has been skipped
ci / image (worker) (push) Has been skipped
ci / bump (push) Has been skipped
ci / lint (push) Failing after 56s
ci / types (push) Has been skipped
ci / unit (push) Has been skipped
ci / integration (push) Has been skipped
ci / security (push) Has been skipped
ci / dockerfile (push) Has been skipped
ci / chart (push) Has been skipped
ci / image (api) (push) Has been skipped
ci / image (reconciler) (push) Has been skipped
ci / image (worker) (push) Has been skipped
ci / bump (push) Has been skipped
FastAPI already served /docs, /redoc and /openapi.json, and the ingress already
passed them through — the mechanism was there, the content was not. The schema
alone cannot tell a caller the three things they most need to know, and the
route docstrings explain implementation reasoning to a maintainer rather than
usage to a consumer.
Added to the spec itself, so it travels with the API rather than living in a
README the caller does not have:
- An app description covering bearer auth, that every write is 202 + poll,
the instance lifecycle, and the uniform {"code", "message"} error body.
- Tag descriptions for `instances` and `ops`.
- Field descriptions and worked examples on CreateInstanceRequest,
InstanceResponse and ErrorBody, so /docs shows a valid payload instead of
leaving callers to infer one.
The bearer scheme was already exposed via HTTPBearer, which is what makes the
Authorize button in /docs work; there is now a test asserting it stays, along
with the description, the tags and the request example. Docs that are not
tested rot silently, and this is the artifact other teams integrate against.
README documents the three URLs and how to generate a client from the spec.
This commit is contained in:
@@ -634,3 +634,40 @@ async def test_auth_disabled_accepts_an_unauthenticated_request(settings: Settin
|
||||
async with httpx.AsyncClient(transport=transport, base_url="http://test") as c:
|
||||
resp = await c.get("/v1/instances")
|
||||
assert resp.status_code == 200
|
||||
|
||||
|
||||
# --------------------------------------------------------------------------- openapi
|
||||
|
||||
|
||||
async def test_openapi_documents_the_api_for_a_caller_without_this_repo(
|
||||
client: httpx.AsyncClient,
|
||||
) -> None:
|
||||
"""/openapi.json is the contract other teams integrate against, so it gets a test.
|
||||
|
||||
Pins the parts a generated schema does not give you for free and that silently rot: the
|
||||
prose description, the tag docs, and the bearer scheme that makes the Authorize button
|
||||
in /docs work. Without the security scheme a caller cannot try a single authenticated
|
||||
route from the UI.
|
||||
"""
|
||||
resp = await client.get("/openapi.json")
|
||||
assert resp.status_code == 200
|
||||
spec = resp.json()
|
||||
|
||||
assert spec["info"]["title"] == "svcforge"
|
||||
# The description carries the two things the schema cannot express: writes are async,
|
||||
# and authorisation is a WHERE clause that 404s rather than 403s.
|
||||
description = spec["info"]["description"]
|
||||
assert "202" in description and "404" in description
|
||||
|
||||
assert {t["name"] for t in spec["tags"]} == {"instances", "ops"}
|
||||
assert "HTTPBearer" in spec["components"]["securitySchemes"]
|
||||
assert "/v1/instances" in spec["paths"]
|
||||
# An example payload, so a caller can see a valid body rather than infer one.
|
||||
assert spec["components"]["schemas"]["CreateInstanceRequest"]["examples"]
|
||||
|
||||
|
||||
async def test_swagger_and_redoc_are_served(client: httpx.AsyncClient) -> None:
|
||||
"""The human-facing docs. Both are on by default; a `docs_url=None` would drop them."""
|
||||
for path in ("/docs", "/redoc"):
|
||||
resp = await client.get(path)
|
||||
assert resp.status_code == 200, path
|
||||
|
||||
Reference in New Issue
Block a user