test(api): contract test proving OpenAPI matches every implemented route - #552
Open
Miracle656 wants to merge 1 commit into
Open
test(api): contract test proving OpenAPI matches every implemented route#552Miracle656 wants to merge 1 commit into
Miracle656 wants to merge 1 commit into
Conversation
|
@Miracle656 Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits. You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀 |
Miracle656
force-pushed
the
test/513-openapi-route-inventory
branch
from
August 30, 2026 14:40
c70f978 to
33f60db
Compare
Author
|
Rebased onto current dev — the deepest of the batch, because #579 landed overlapping work on both sides of this PR's contract while it was open. How each collision resolved:
Full Go suite green ( |
The spec had drifted badly: 13 live REST operations (all eight webhook
endpoints, the admin contracts CRUD, PATCH /v1/api-keys/{id}, GET
/v1/admin/keys/{id}/usage, POST /v1/contracts/{id}/call) had no spec
entry at all, and nothing could catch the next one — route registration
was inline in main() with live dependencies, so no test could enumerate
the router, and Go's ServeMux cannot list its own patterns.
Route registration now lives in routes.go as a single table of
(route, lazily-bound handler) pairs: main() registers from it, and the
new inventory test reads it through routeInventory() without touching a
handler. The same literal is simultaneously the registration source of
truth and the test inventory, so route<->spec drift is structurally
impossible to reintroduce. Every route is either documented in
api/openapi.yaml or carries an explicit exemption reason (/internal/
status, /ws, /graphql — non-REST surfaces documented elsewhere); there
is no third state, and a route added without deciding fails the test.
Two tests run in the ordinary go test job on every change:
- TestEveryRouteIsDocumentedOrExempted fails when a route exists
without a spec entry or a spec entry without a route, in either
direction.
- TestEveryOperationDocumentsStatusCodesAndErrorEnvelope fails when an
operation documents no success or no error status, or when a JSON
error body is not the canonical ErrorResponse envelope — status codes
and error envelopes, not just paths. Deliberate exceptions (the
readiness 503 returns check detail; three operations with no error
contract by design) are explicit allowlists with reasons.
The 13 missing operations are now documented with their real shapes,
verbatim from the handlers: webhook camelCase bodies (and the replay
endpoint's snake_case), plain-text error responses where that is what
the handler emits, nullable list responses, the contract-call
endpoint's three success shapes, and admin auth via X-Admin-Key. The
admin-contracts handlers moved from a legacy {"error":{"message"}}
shape to the canonical envelope as part of being documented; the
unmounted usage handlers keep the legacy helper with a note. SDK models
are regenerated from the spec (all four generated files); spectral is
clean apart from the pre-existing orphaned TokenMetadataResponse
schema, and the new Webhooks/Contracts tags are declared.
The live e2e suite's operation-coverage assertion defers the 14 newly
documented operations via an explicit burn-down list (following its
existing getAdminDbStats precedent) — they need stateful fixtures the
compose stack does not seed yet; their spec agreement and error
contracts are enforced by the static tests above.
Closes Telocel-Labs#513
Miracle656
force-pushed
the
test/513-openapi-route-inventory
branch
from
August 30, 2026 14:46
33f60db to
110524f
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #513 (related to #421, #232)
Problem
The spec had drifted badly — 13 live REST operations had no spec entry (all eight webhook endpoints, admin contracts CRUD,
PATCH /v1/api-keys/{id},GET /v1/admin/keys/{id}/usage,POST /v1/contracts/{id}/call) — and nothing could catch the next one: registration was inline inmain()with live dependencies, and Go's ServeMux cannot enumerate its own patterns.What this does
services/api/routes.go): one literal of (route, lazily-bound handler) pairs is simultaneously whatmain()registers and what the test enumerates (no handler construction needed) — drift is structurally impossible to reintroduce. Every route is documented or carries an explicit exemption reason (/internal/status,/ws,/graphql); there is no third state.go testjob (fail at the PR, not at release): route↔spec set equality in both directions; and per-operation status-code/error-envelope completeness — every operation documents at least one success and one error status, and every JSON error body must be the canonicalErrorResponse(deliberate exceptions are explicit allowlists with reasons).X-Admin-Keyauth. SDK models regenerated (all four generated files); spectral clean apart from the pre-existing orphanedTokenMetadataResponseschema.resolveAPIKeyIDtreated the rawX-API-Keyheader as anapi_keys.idUUID and fell back toINSERT INTO api_keys DEFAULT VALUES(violating NOT NULL constraints) — so every legitimateGET/POST /v1/webhookscall 500'd before reaching a subscription. It now uses the key ID the auth middleware already resolved; legacy env-hash keys get an explicit canonical 401 instead of a stray row insert.by_endpointin the admin usage report serialized asnullon empty windows, which the generated Rust (Vec<EndpointUsage>) and Python models reject — it now always serializes[]and the spec says so.The admin-contracts handlers also moved from a legacy
{"error":{"message"}}shape to the canonical envelope as part of being documented (the unmounted usage handlers keep the legacy helper with a note).getAdminDbStatsprecedent) — they need stateful fixtures the compose stack does not seed; their spec agreement and error contracts are enforced statically.Done-when check
CI fails on any divergence between the spec and the implemented API — the new tests pass against the fixed spec and fail when either side drifts. Full Go suite, spectral, SDK-version check, and generated-model freshness all green locally.