feat(api,cli): strict UUID validation for app-id and workspace-id (#37212)

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
This commit is contained in:
Xiyuan Chen
2026-06-09 07:35:18 +00:00
committed by GitHub
co-authored by autofix-ci[bot]
parent 686e643632
commit 1502a57381
19 changed files with 99 additions and 92 deletions
@@ -32,18 +32,27 @@ def _make_auth_data(**kwargs) -> AuthData:
return data
_VALID_APP_UUID = "00000000-0000-0000-0000-000000000001"
def test_load_app_writes_app_to_data():
app = MagicMock()
app.status = "normal"
app.enable_api = True
data = _make_auth_data(path_params={"app_id": "abc"})
data = _make_auth_data(path_params={"app_id": _VALID_APP_UUID})
with patch("controllers.openapi.auth.prepare.AppService.get_app_by_id", return_value=app):
load_app(data)
assert data.app is app
def test_load_app_raises_not_found_for_non_uuid_app_id():
data = _make_auth_data(path_params={"app_id": "not-a-uuid"})
with pytest.raises(NotFound):
load_app(data)
def test_load_app_raises_not_found_when_missing():
data = _make_auth_data(path_params={"app_id": "missing"})
data = _make_auth_data(path_params={"app_id": _VALID_APP_UUID})
with patch("controllers.openapi.auth.prepare.AppService.get_app_by_id", return_value=None):
with pytest.raises(NotFound):
load_app(data)
@@ -52,7 +61,7 @@ def test_load_app_raises_not_found_when_missing():
def test_load_app_raises_not_found_when_not_normal():
app = MagicMock()
app.status = "archived"
data = _make_auth_data(path_params={"app_id": "abc"})
data = _make_auth_data(path_params={"app_id": _VALID_APP_UUID})
with patch("controllers.openapi.auth.prepare.AppService.get_app_by_id", return_value=app):
with pytest.raises(NotFound):
load_app(data)
@@ -62,7 +71,7 @@ def test_load_app_stashes_app_even_when_api_disabled():
app = MagicMock()
app.status = "normal"
app.enable_api = False
data = _make_auth_data(path_params={"app_id": "abc"})
data = _make_auth_data(path_params={"app_id": _VALID_APP_UUID})
with patch("controllers.openapi.auth.prepare.AppService.get_app_by_id", return_value=app):
load_app(data)
assert data.app is app
@@ -18,8 +18,8 @@ from controllers.openapi.apps import AppListQuery
def test_defaults():
q = AppListQuery.model_validate({"workspace_id": "ws-1"})
assert q.workspace_id == "ws-1"
q = AppListQuery.model_validate({"workspace_id": "00000000-0000-0000-0000-000000000001"})
assert q.workspace_id == "00000000-0000-0000-0000-000000000001"
assert q.page == 1
assert q.limit == 20
assert q.mode is None
@@ -34,61 +34,63 @@ def test_workspace_id_required():
def test_page_must_be_positive():
with pytest.raises(ValidationError):
AppListQuery.model_validate({"workspace_id": "ws-1", "page": 0})
AppListQuery.model_validate({"workspace_id": "00000000-0000-0000-0000-000000000001", "page": 0})
with pytest.raises(ValidationError):
AppListQuery.model_validate({"workspace_id": "ws-1", "page": -1})
AppListQuery.model_validate({"workspace_id": "00000000-0000-0000-0000-000000000001", "page": -1})
def test_page_rejects_non_integer_string():
with pytest.raises(ValidationError):
AppListQuery.model_validate({"workspace_id": "ws-1", "page": "abc"})
AppListQuery.model_validate({"workspace_id": "00000000-0000-0000-0000-000000000001", "page": "abc"})
def test_limit_must_be_positive():
with pytest.raises(ValidationError):
AppListQuery.model_validate({"workspace_id": "ws-1", "limit": 0})
AppListQuery.model_validate({"workspace_id": "00000000-0000-0000-0000-000000000001", "limit": 0})
with pytest.raises(ValidationError):
AppListQuery.model_validate({"workspace_id": "ws-1", "limit": -1})
AppListQuery.model_validate({"workspace_id": "00000000-0000-0000-0000-000000000001", "limit": -1})
def test_limit_caps_at_max_page_limit():
# Boundary accepts.
q = AppListQuery.model_validate({"workspace_id": "ws-1", "limit": MAX_PAGE_LIMIT})
q = AppListQuery.model_validate({"workspace_id": "00000000-0000-0000-0000-000000000001", "limit": MAX_PAGE_LIMIT})
assert q.limit == MAX_PAGE_LIMIT
# Just over rejects.
with pytest.raises(ValidationError):
AppListQuery.model_validate({"workspace_id": "ws-1", "limit": MAX_PAGE_LIMIT + 1})
AppListQuery.model_validate(
{"workspace_id": "00000000-0000-0000-0000-000000000001", "limit": MAX_PAGE_LIMIT + 1}
)
def test_mode_whitelisted_against_app_mode():
# Valid mode passes.
q = AppListQuery.model_validate({"workspace_id": "ws-1", "mode": "chat"})
q = AppListQuery.model_validate({"workspace_id": "00000000-0000-0000-0000-000000000001", "mode": "chat"})
assert q.mode is not None
assert q.mode.value == "chat"
# Invalid mode rejects.
with pytest.raises(ValidationError):
AppListQuery.model_validate({"workspace_id": "ws-1", "mode": "not-a-mode"})
AppListQuery.model_validate({"workspace_id": "00000000-0000-0000-0000-000000000001", "mode": "not-a-mode"})
def test_name_length_capped():
AppListQuery.model_validate({"workspace_id": "ws-1", "name": "x" * 200})
AppListQuery.model_validate({"workspace_id": "00000000-0000-0000-0000-000000000001", "name": "x" * 200})
with pytest.raises(ValidationError):
AppListQuery.model_validate({"workspace_id": "ws-1", "name": "x" * 201})
AppListQuery.model_validate({"workspace_id": "00000000-0000-0000-0000-000000000001", "name": "x" * 201})
def test_tag_length_capped():
AppListQuery.model_validate({"workspace_id": "ws-1", "tag": "x" * 100})
AppListQuery.model_validate({"workspace_id": "00000000-0000-0000-0000-000000000001", "tag": "x" * 100})
with pytest.raises(ValidationError):
AppListQuery.model_validate({"workspace_id": "ws-1", "tag": "x" * 101})
AppListQuery.model_validate({"workspace_id": "00000000-0000-0000-0000-000000000001", "tag": "x" * 101})
def test_all_fields_accept_valid_values():
"""Pin the happy-path acceptance for every field in one place."""
q = AppListQuery.model_validate(
{
"workspace_id": "ws-1",
"workspace_id": "00000000-0000-0000-0000-000000000001",
"page": 5,
"limit": 50,
"mode": "workflow",
@@ -96,7 +98,7 @@ def test_all_fields_accept_valid_values():
"tag": "prod",
}
)
assert q.workspace_id == "ws-1"
assert q.workspace_id == "00000000-0000-0000-0000-000000000001"
assert q.page == 5
assert q.limit == 50
assert q.mode is not None