mirror of
https://github.com/dograh-hq/dograh.git
synced 2026-06-19 08:28:10 +02:00
fix: validate workflow status filter to prevent 500 on invalid enum value (#450)
* Validate workflow status filter to prevent 500 on invalid enum value The /workflow/fetch and /workflow/summary endpoints accepted a free-form status query param and passed it straight into a query that casts to the workflow_status PG enum (active/archived). Any other value — e.g. an external caller passing 'published' (a workflow_definitions version state, not a workflow status) — failed deep in Postgres as InvalidTextRepresentationError, surfacing as an unhandled HTTP 500. Add _validate_status_filter() to reject values outside WorkflowStatus with a clean 422 before any DB query, for both the single and comma-separated paths. Add route tests covering invalid, valid-single, comma-separated, and mixed valid/invalid cases. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * chore: add tests --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
9a1b980f91
commit
d2cda85b78
5 changed files with 186 additions and 26 deletions
|
|
@ -2,6 +2,7 @@ from datetime import datetime, timezone
|
|||
from types import SimpleNamespace
|
||||
from unittest.mock import AsyncMock, patch
|
||||
|
||||
import pytest
|
||||
from fastapi import FastAPI
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
|
|
@ -50,3 +51,99 @@ def test_workflow_fetch_list_includes_workflow_uuid():
|
|||
"workflow_uuid": workflow.workflow_uuid,
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
def test_workflow_fetch_invalid_status_returns_422_without_db_query():
|
||||
"""A status outside the workflow_status enum (e.g. 'published') must fail
|
||||
as a clean 422 instead of a 500 from the Postgres enum cast."""
|
||||
app = _make_test_app()
|
||||
client = TestClient(app)
|
||||
|
||||
with patch("api.routes.workflow.db_client") as mock_db:
|
||||
mock_db.get_all_workflows_for_listing = AsyncMock()
|
||||
mock_db.get_workflow_run_counts = AsyncMock()
|
||||
|
||||
response = client.get("/workflow/fetch?status=published")
|
||||
|
||||
assert response.status_code == 422
|
||||
assert "published" in response.json()["detail"]
|
||||
# The invalid value must never reach the database layer.
|
||||
mock_db.get_all_workflows_for_listing.assert_not_called()
|
||||
|
||||
|
||||
def test_workflow_fetch_valid_single_status_passes_through():
|
||||
app = _make_test_app()
|
||||
client = TestClient(app)
|
||||
|
||||
with patch("api.routes.workflow.db_client") as mock_db:
|
||||
mock_db.get_all_workflows_for_listing = AsyncMock(return_value=[])
|
||||
mock_db.get_workflow_run_counts = AsyncMock(return_value={})
|
||||
|
||||
response = client.get("/workflow/fetch?status=active")
|
||||
|
||||
assert response.status_code == 200
|
||||
mock_db.get_all_workflows_for_listing.assert_awaited_once_with(
|
||||
organization_id=11, status="active"
|
||||
)
|
||||
|
||||
|
||||
def test_workflow_fetch_comma_separated_status_queries_each_value():
|
||||
app = _make_test_app()
|
||||
client = TestClient(app)
|
||||
|
||||
with patch("api.routes.workflow.db_client") as mock_db:
|
||||
mock_db.get_all_workflows_for_listing = AsyncMock(return_value=[])
|
||||
mock_db.get_workflow_run_counts = AsyncMock(return_value={})
|
||||
|
||||
response = client.get("/workflow/fetch?status=active,archived")
|
||||
|
||||
assert response.status_code == 200
|
||||
assert mock_db.get_all_workflows_for_listing.await_count == 2
|
||||
statuses = {
|
||||
call.kwargs["status"]
|
||||
for call in mock_db.get_all_workflows_for_listing.await_args_list
|
||||
}
|
||||
assert statuses == {"active", "archived"}
|
||||
|
||||
|
||||
def test_workflow_fetch_mixed_valid_and_invalid_status_returns_422():
|
||||
app = _make_test_app()
|
||||
client = TestClient(app)
|
||||
|
||||
with patch("api.routes.workflow.db_client") as mock_db:
|
||||
mock_db.get_all_workflows_for_listing = AsyncMock()
|
||||
mock_db.get_workflow_run_counts = AsyncMock()
|
||||
|
||||
response = client.get("/workflow/fetch?status=active,published")
|
||||
|
||||
assert response.status_code == 422
|
||||
mock_db.get_all_workflows_for_listing.assert_not_called()
|
||||
|
||||
|
||||
@pytest.mark.parametrize("status", [" ", ",", "active,,archived"])
|
||||
def test_workflow_fetch_blank_status_token_returns_422_without_db_query(status: str):
|
||||
app = _make_test_app()
|
||||
client = TestClient(app)
|
||||
|
||||
with patch("api.routes.workflow.db_client") as mock_db:
|
||||
mock_db.get_all_workflows_for_listing = AsyncMock()
|
||||
mock_db.get_workflow_run_counts = AsyncMock()
|
||||
|
||||
response = client.get("/workflow/fetch", params={"status": status})
|
||||
|
||||
assert response.status_code == 422
|
||||
assert "<empty>" in response.json()["detail"]
|
||||
mock_db.get_all_workflows_for_listing.assert_not_called()
|
||||
|
||||
|
||||
def test_workflow_summary_blank_status_token_returns_422_without_db_query():
|
||||
app = _make_test_app()
|
||||
client = TestClient(app)
|
||||
|
||||
with patch("api.routes.workflow.db_client") as mock_db:
|
||||
mock_db.get_all_workflows = AsyncMock()
|
||||
|
||||
response = client.get("/workflow/summary", params={"status": ","})
|
||||
|
||||
assert response.status_code == 422
|
||||
mock_db.get_all_workflows.assert_not_called()
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import pytest
|
|||
|
||||
from api.services import workflow_run_billing as workflow_run_billing_mod
|
||||
from api.services.workflow_run_billing import (
|
||||
_is_usage_not_ready_error,
|
||||
report_completed_workflow_run_platform_usage,
|
||||
report_workflow_run_platform_usage,
|
||||
)
|
||||
|
|
@ -24,6 +25,16 @@ def _make_workflow_run():
|
|||
)
|
||||
|
||||
|
||||
def test_is_usage_not_ready_error_detects_mps_409():
|
||||
exc = Exception("Failed to report platform usage")
|
||||
exc.response = SimpleNamespace(
|
||||
status_code=409,
|
||||
text='{"detail":"usage_not_ready"}',
|
||||
)
|
||||
|
||||
assert _is_usage_not_ready_error(exc) is True
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_report_workflow_run_platform_usage_reports_hosted_completion(
|
||||
monkeypatch,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue