feat(openapi): declare the full response contract (401/404/409/413) on every v1 route

Honors portability safeguard (c) -- the committed docs/openapi-v1.json was
enforced to stay in sync with app.openapi() (test_openapi_committed.py),
but the app declared almost no error responses: POST /v1/emissoes showed
only 200/422 (no 201), and no route declared 401/404/409/413 or the
structured 409 codes the spec names as contract. A Go reimplementation
reading only the committed OpenAPI as source of truth wouldn't learn them.

Adds responses={...} to every v1 router (emissao, certificados, series,
documentos GET/xml) covering the status codes each route actually returns
-- 201 as the default on POST /v1/emissoes (with 200 documented for the
Idempotency-Key replay case), 401 on every authenticated route, 404 where
the anti-oracle boundary applies, 409 naming the structured codes each
route raises, 413 on the certificate upload's size cap. Regenerated
docs/openapi-v1.json from app.openapi() (uv run python -c '...json.dump...'
per test_openapi_committed.py's own docstring) so the committed==generated
assertion stays green with FIX 1-4's new 409 codes included.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
jonatanritter
2026-08-08 15:36:58 -03:00
co-authored by Claude Opus 4.8
parent 260644c961
commit c2d2d30b70
4 changed files with 177 additions and 19 deletions
+32 -3
View File
@@ -41,7 +41,22 @@ async def _read_upload_capped(file: UploadFile, max_bytes: int) -> bytes:
return b"".join(chunks)
@router.post("", response_model=FiscalCertificateRead, status_code=status.HTTP_201_CREATED)
@router.post(
"",
response_model=FiscalCertificateRead,
status_code=status.HTTP_201_CREATED,
responses={
401: {"description": "API key ausente ou inválida"},
409: {"description": "`detail.code`=`certificate_upload_conflict` -- upload concorrente venceu a corrida"},
413: {"description": f"Certificado excede o tamanho máximo permitido ({_MAX_PFX_UPLOAD_BYTES} bytes)"},
422: {
"description": (
"PFX inválido/senha incorreta, CNPJ do certificado diverge do declarado, "
"certificado vencido ou ainda não vigente"
)
},
},
)
async def upload_certificate_endpoint(
tenant_ref: str = Query(..., max_length=64),
branch_ref: str = Query(..., max_length=64),
@@ -77,7 +92,14 @@ async def upload_certificate_endpoint(
return FiscalCertificateRead.model_validate(certificate)
@router.get("", response_model=FiscalCertificateRead)
@router.get(
"",
response_model=FiscalCertificateRead,
responses={
401: {"description": "API key ausente ou inválida"},
404: {"description": "Nenhum certificado vivo para este (tenant_ref, branch_ref) -- anti-oracle"},
},
)
async def read_certificate_endpoint(
tenant_ref: str = Query(..., max_length=64),
branch_ref: str = Query(..., max_length=64),
@@ -96,7 +118,14 @@ async def read_certificate_endpoint(
return FiscalCertificateRead.model_validate(certificate)
@router.delete("", status_code=status.HTTP_204_NO_CONTENT)
@router.delete(
"",
status_code=status.HTTP_204_NO_CONTENT,
responses={
401: {"description": "API key ausente ou inválida"},
404: {"description": "Nenhum certificado vivo para este (tenant_ref, branch_ref) -- anti-oracle"},
},
)
async def deactivate_certificate_endpoint(
tenant_ref: str = Query(..., max_length=64),
branch_ref: str = Query(..., max_length=64),