mirror of
https://github.com/dograh-hq/dograh.git
synced 2026-07-19 11:41:04 +02:00
* fix(api): validate pagination bounds on run-list endpoints (#553) GET /workflow/{id}/runs and GET /campaign/{id}/runs declared bare `page: int = 1` / `limit: int = 50` params, then computed `total_pages = (total_count + limit - 1) // limit`. A `?limit=0` raised an unhandled ZeroDivisionError (HTTP 500), and negative limit/page produced a negative offset and nonsensical pagination. Add `Query(ge=1, le=100)` / `Query(1, ge=1)` bounds to both endpoints, matching the sibling list endpoints (/usage/runs and the superuser runs endpoint) that already validate these. Out-of-range values now return 422. Adds a regression test covering limit=0/-5/101 and page=0 on both endpoints. Fixes #553 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * chore(docs): regenerate openapi.json for run-list pagination bounds (#553) The added Query(ge/le) bounds on the workflow-run and campaign-run list endpoints changed the OpenAPI schema; regenerate the committed spec via `python -m scripts.dump_docs_openapi` so the drift-check passes. Only the limit/page parameter schemas for those two endpoints change. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> Co-authored-by: Abhishek Kumar <abhishek@a6k.me>
This commit is contained in:
parent
ade0ee9104
commit
9471041b8b
4 changed files with 45 additions and 5 deletions
|
|
@ -696,8 +696,8 @@ async def update_campaign(
|
|||
@router.get("/{campaign_id}/runs")
|
||||
async def get_campaign_runs(
|
||||
campaign_id: int,
|
||||
page: int = 1,
|
||||
limit: int = 50,
|
||||
page: int = Query(1, ge=1, description="Page number (starts from 1)"),
|
||||
limit: int = Query(50, ge=1, le=100, description="Number of items per page"),
|
||||
filters: Optional[str] = Query(None, description="JSON-encoded filter criteria"),
|
||||
sort_by: Optional[str] = Query(
|
||||
None, description="Field to sort by (e.g., 'duration', 'created_at')"
|
||||
|
|
|
|||
|
|
@ -1392,8 +1392,8 @@ class WorkflowRunsResponse(BaseModel):
|
|||
@router.get("/{workflow_id}/runs")
|
||||
async def get_workflow_runs(
|
||||
workflow_id: int,
|
||||
page: int = 1,
|
||||
limit: int = 50,
|
||||
page: int = Query(1, ge=1, description="Page number (starts from 1)"),
|
||||
limit: int = Query(50, ge=1, le=100, description="Number of items per page"),
|
||||
filters: Optional[str] = Query(None, description="JSON-encoded filter criteria"),
|
||||
sort_by: Optional[str] = Query(
|
||||
None, description="Field to sort by (e.g., 'duration', 'created_at')"
|
||||
|
|
|
|||
40
api/tests/test_run_list_pagination_bounds.py
Normal file
40
api/tests/test_run_list_pagination_bounds.py
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
"""Pagination bounds for the workflow-run and campaign-run list endpoints.
|
||||
|
||||
Regression for issue #553: `limit=0` raised an unhandled ZeroDivisionError
|
||||
(HTTP 500) in the `total_pages` computation, and negative `limit`/`page`
|
||||
produced nonsensical pagination. Both endpoints now validate the params
|
||||
(`limit` in [1, 100], `page` >= 1), matching the sibling list endpoints.
|
||||
"""
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
async def _make_user(db_session, slug: str):
|
||||
user, _ = await db_session.get_or_create_user_by_provider_id(f"{slug}_user")
|
||||
org, _ = await db_session.get_or_create_organization_by_provider_id(
|
||||
f"{slug}_org", user.id
|
||||
)
|
||||
await db_session.update_user_selected_organization(user.id, org.id)
|
||||
return await db_session.get_user_by_id(user.id)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"path",
|
||||
[
|
||||
"/api/v1/workflow/1/runs",
|
||||
"/api/v1/campaign/1/runs",
|
||||
],
|
||||
)
|
||||
@pytest.mark.parametrize("query", ["limit=0", "limit=-5", "limit=101", "page=0"])
|
||||
async def test_run_list_rejects_out_of_range_pagination(
|
||||
test_client_factory, db_session, path, query
|
||||
):
|
||||
"""Out-of-range limit/page is a 422 validation error, never a 500."""
|
||||
user = await _make_user(db_session, "paginate_bounds")
|
||||
|
||||
async with test_client_factory(user) as client:
|
||||
response = await client.get(f"{path}?{query}")
|
||||
|
||||
assert response.status_code == 422, (
|
||||
f"{path}?{query} expected 422, got {response.status_code}: {response.text}"
|
||||
)
|
||||
File diff suppressed because one or more lines are too long
Loading…
Add table
Add a link
Reference in a new issue