fix(api): document missing HTTP status codes on router endpoints
All checks were successful
CI / Lint (ruff) (push) Successful in 16s
CI / SAST (bandit) (push) Successful in 18s
CI / Dependency audit (pip-audit) (push) Successful in 26s
CI / Test (Standard) (3.11) (push) Successful in 2m41s
CI / Test (Live) (3.11) (push) Successful in 1m6s
CI / Test (Fuzz) (3.11) (push) Successful in 1h9m14s
CI / Finalize Merge to Main (push) Has been skipped
CI / Merge dev → testing (push) Successful in 12s
CI / Prepare Merge to Main (push) Has been skipped
All checks were successful
CI / Lint (ruff) (push) Successful in 16s
CI / SAST (bandit) (push) Successful in 18s
CI / Dependency audit (pip-audit) (push) Successful in 26s
CI / Test (Standard) (3.11) (push) Successful in 2m41s
CI / Test (Live) (3.11) (push) Successful in 1m6s
CI / Test (Fuzz) (3.11) (push) Successful in 1h9m14s
CI / Finalize Merge to Main (push) Has been skipped
CI / Merge dev → testing (push) Successful in 12s
CI / Prepare Merge to Main (push) Has been skipped
Schemathesis was failing CI on routes that returned status codes not declared in their OpenAPI responses= dicts. Adds the missing codes across swarm_updates, swarm_mgmt, swarm, fleet and attackers routers. Also adds 400 to every POST/PUT/PATCH that accepts a JSON body — Starlette returns 400 on malformed/non-UTF8 bodies before FastAPI's 422 validation runs, which schemathesis fuzzing trips every time. No handler logic changed.
This commit is contained in:
@@ -15,6 +15,7 @@ router = APIRouter()
|
|||||||
401: {"description": "Could not validate credentials"},
|
401: {"description": "Could not validate credentials"},
|
||||||
403: {"description": "Insufficient permissions"},
|
403: {"description": "Insufficient permissions"},
|
||||||
404: {"description": "Attacker not found"},
|
404: {"description": "Attacker not found"},
|
||||||
|
422: {"description": "Query parameter validation error (limit/offset out of range or invalid)"},
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
@_traced("api.get_attacker_commands")
|
@_traced("api.get_attacker_commands")
|
||||||
|
|||||||
@@ -11,7 +11,12 @@ router = APIRouter()
|
|||||||
@router.post(
|
@router.post(
|
||||||
"/deckies/{decky_name}/mutate",
|
"/deckies/{decky_name}/mutate",
|
||||||
tags=["Fleet Management"],
|
tags=["Fleet Management"],
|
||||||
responses={401: {"description": "Could not validate credentials"}, 403: {"description": "Insufficient permissions"}, 404: {"description": "Decky not found"}}
|
responses={
|
||||||
|
401: {"description": "Could not validate credentials"},
|
||||||
|
403: {"description": "Insufficient permissions"},
|
||||||
|
404: {"description": "Decky not found"},
|
||||||
|
422: {"description": "Path parameter validation error (decky_name must match ^[a-z0-9\\-]{1,64}$)"},
|
||||||
|
}
|
||||||
)
|
)
|
||||||
@_traced("api.mutate_decky")
|
@_traced("api.mutate_decky")
|
||||||
async def api_mutate_decky(
|
async def api_mutate_decky(
|
||||||
|
|||||||
@@ -29,7 +29,11 @@ router = APIRouter()
|
|||||||
response_model=SwarmEnrolledBundle,
|
response_model=SwarmEnrolledBundle,
|
||||||
status_code=status.HTTP_201_CREATED,
|
status_code=status.HTTP_201_CREATED,
|
||||||
tags=["Swarm Hosts"],
|
tags=["Swarm Hosts"],
|
||||||
responses={409: {"description": "A worker with this name is already enrolled"}},
|
responses={
|
||||||
|
400: {"description": "Bad Request (malformed JSON body)"},
|
||||||
|
409: {"description": "A worker with this name is already enrolled"},
|
||||||
|
422: {"description": "Request body validation error"},
|
||||||
|
},
|
||||||
)
|
)
|
||||||
async def api_enroll_host(
|
async def api_enroll_host(
|
||||||
req: SwarmEnrollRequest,
|
req: SwarmEnrollRequest,
|
||||||
|
|||||||
@@ -101,8 +101,10 @@ async def _verify_peer_matches_host(
|
|||||||
status_code=204,
|
status_code=204,
|
||||||
tags=["Swarm Health"],
|
tags=["Swarm Health"],
|
||||||
responses={
|
responses={
|
||||||
|
400: {"description": "Bad Request (malformed JSON body)"},
|
||||||
403: {"description": "Peer cert missing, or its fingerprint does not match the host's pinned cert"},
|
403: {"description": "Peer cert missing, or its fingerprint does not match the host's pinned cert"},
|
||||||
404: {"description": "host_uuid is not enrolled"},
|
404: {"description": "host_uuid is not enrolled"},
|
||||||
|
422: {"description": "Request body validation error"},
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
async def heartbeat(
|
async def heartbeat(
|
||||||
|
|||||||
@@ -25,7 +25,11 @@ router = APIRouter()
|
|||||||
"/teardown",
|
"/teardown",
|
||||||
response_model=SwarmDeployResponse,
|
response_model=SwarmDeployResponse,
|
||||||
tags=["Swarm Deployments"],
|
tags=["Swarm Deployments"],
|
||||||
responses={404: {"description": "A targeted host does not exist"}},
|
responses={
|
||||||
|
400: {"description": "Bad Request (malformed JSON body)"},
|
||||||
|
404: {"description": "A targeted host does not exist"},
|
||||||
|
422: {"description": "Request body validation error"},
|
||||||
|
},
|
||||||
)
|
)
|
||||||
async def api_teardown_swarm(
|
async def api_teardown_swarm(
|
||||||
req: SwarmTeardownRequest,
|
req: SwarmTeardownRequest,
|
||||||
|
|||||||
@@ -24,6 +24,12 @@ router = APIRouter()
|
|||||||
"/hosts/{uuid}",
|
"/hosts/{uuid}",
|
||||||
status_code=status.HTTP_204_NO_CONTENT,
|
status_code=status.HTTP_204_NO_CONTENT,
|
||||||
tags=["Swarm Management"],
|
tags=["Swarm Management"],
|
||||||
|
responses={
|
||||||
|
401: {"description": "Could not validate credentials"},
|
||||||
|
403: {"description": "Insufficient permissions"},
|
||||||
|
404: {"description": "Host not found"},
|
||||||
|
422: {"description": "Path parameter validation error"},
|
||||||
|
},
|
||||||
)
|
)
|
||||||
async def decommission_host(
|
async def decommission_host(
|
||||||
uuid: str,
|
uuid: str,
|
||||||
|
|||||||
@@ -322,6 +322,13 @@ def _render_bootstrap(
|
|||||||
response_model=EnrollBundleResponse,
|
response_model=EnrollBundleResponse,
|
||||||
status_code=status.HTTP_201_CREATED,
|
status_code=status.HTTP_201_CREATED,
|
||||||
tags=["Swarm Management"],
|
tags=["Swarm Management"],
|
||||||
|
responses={
|
||||||
|
400: {"description": "Bad Request (malformed JSON body)"},
|
||||||
|
401: {"description": "Could not validate credentials"},
|
||||||
|
403: {"description": "Insufficient permissions"},
|
||||||
|
409: {"description": "A worker with this name is already enrolled"},
|
||||||
|
422: {"description": "Request body validation error"},
|
||||||
|
},
|
||||||
)
|
)
|
||||||
async def create_enroll_bundle(
|
async def create_enroll_bundle(
|
||||||
req: EnrollBundleRequest,
|
req: EnrollBundleRequest,
|
||||||
|
|||||||
@@ -115,6 +115,13 @@ async def _run_teardown(
|
|||||||
response_model=TeardownHostResponse,
|
response_model=TeardownHostResponse,
|
||||||
status_code=status.HTTP_202_ACCEPTED,
|
status_code=status.HTTP_202_ACCEPTED,
|
||||||
tags=["Swarm Management"],
|
tags=["Swarm Management"],
|
||||||
|
responses={
|
||||||
|
400: {"description": "Bad Request (malformed JSON body)"},
|
||||||
|
401: {"description": "Could not validate credentials"},
|
||||||
|
403: {"description": "Insufficient permissions"},
|
||||||
|
404: {"description": "Host not found"},
|
||||||
|
422: {"description": "Request body or path parameter validation error"},
|
||||||
|
},
|
||||||
)
|
)
|
||||||
async def teardown_host(
|
async def teardown_host(
|
||||||
uuid: str,
|
uuid: str,
|
||||||
|
|||||||
@@ -64,6 +64,10 @@ async def _probe_host(host: dict[str, Any]) -> HostReleaseInfo:
|
|||||||
"/hosts",
|
"/hosts",
|
||||||
response_model=HostReleasesResponse,
|
response_model=HostReleasesResponse,
|
||||||
tags=["Swarm Updates"],
|
tags=["Swarm Updates"],
|
||||||
|
responses={
|
||||||
|
401: {"description": "Could not validate credentials"},
|
||||||
|
403: {"description": "Insufficient permissions"},
|
||||||
|
},
|
||||||
)
|
)
|
||||||
async def api_list_host_releases(
|
async def api_list_host_releases(
|
||||||
admin: dict = Depends(require_admin),
|
admin: dict = Depends(require_admin),
|
||||||
|
|||||||
@@ -128,6 +128,13 @@ def _is_expected_connection_drop(exc: BaseException) -> bool:
|
|||||||
"/push",
|
"/push",
|
||||||
response_model=PushUpdateResponse,
|
response_model=PushUpdateResponse,
|
||||||
tags=["Swarm Updates"],
|
tags=["Swarm Updates"],
|
||||||
|
responses={
|
||||||
|
400: {"description": "Bad Request (malformed JSON body or conflicting host_uuids/all flags)"},
|
||||||
|
401: {"description": "Could not validate credentials"},
|
||||||
|
403: {"description": "Insufficient permissions"},
|
||||||
|
404: {"description": "No matching target hosts or no updater-capable hosts enrolled"},
|
||||||
|
422: {"description": "Request body validation error"},
|
||||||
|
},
|
||||||
)
|
)
|
||||||
async def api_push_update(
|
async def api_push_update(
|
||||||
req: PushUpdateRequest,
|
req: PushUpdateRequest,
|
||||||
|
|||||||
@@ -68,6 +68,13 @@ async def _push_self_one(host: dict[str, Any], tarball: bytes, sha: str) -> Push
|
|||||||
"/push-self",
|
"/push-self",
|
||||||
response_model=PushUpdateResponse,
|
response_model=PushUpdateResponse,
|
||||||
tags=["Swarm Updates"],
|
tags=["Swarm Updates"],
|
||||||
|
responses={
|
||||||
|
400: {"description": "Bad Request (malformed JSON body or conflicting host_uuids/all flags)"},
|
||||||
|
401: {"description": "Could not validate credentials"},
|
||||||
|
403: {"description": "Insufficient permissions"},
|
||||||
|
404: {"description": "No matching target hosts or no updater-capable hosts enrolled"},
|
||||||
|
422: {"description": "Request body validation error"},
|
||||||
|
},
|
||||||
)
|
)
|
||||||
async def api_push_update_self(
|
async def api_push_update_self(
|
||||||
req: PushUpdateRequest,
|
req: PushUpdateRequest,
|
||||||
|
|||||||
@@ -23,6 +23,13 @@ router = APIRouter()
|
|||||||
"/rollback",
|
"/rollback",
|
||||||
response_model=RollbackResponse,
|
response_model=RollbackResponse,
|
||||||
tags=["Swarm Updates"],
|
tags=["Swarm Updates"],
|
||||||
|
responses={
|
||||||
|
400: {"description": "Bad Request (malformed JSON body or host has no updater bundle)"},
|
||||||
|
401: {"description": "Could not validate credentials"},
|
||||||
|
403: {"description": "Insufficient permissions"},
|
||||||
|
404: {"description": "Unknown host, or no previous release slot on the worker"},
|
||||||
|
422: {"description": "Request body validation error"},
|
||||||
|
},
|
||||||
)
|
)
|
||||||
async def api_rollback_host(
|
async def api_rollback_host(
|
||||||
req: RollbackRequest,
|
req: RollbackRequest,
|
||||||
|
|||||||
Reference in New Issue
Block a user