litellm/tests/test_litellm/proxy/management_endpoints
yuneng-jiang 416e398154
feat(proxy): add generic list handler for /management/v1 (#35308)
* feat(proxy): add generic list handler for /management/v1

Adds the ListSpec/QueryPlan machinery the control-plane list endpoints are
meant to share, so a resource declares what it exposes instead of hand-rolling
its own paging, sorting and filter parsing.

build_query_plan is pure: it turns query parameters into a QueryPlan or an
RFC 9457 problem without any I/O, which is what lets the plan be asserted as a
value. The database half is a ListExecutor protocol injected by the caller, so
this module has no Prisma dependency at all.

Four things the framework guarantees rather than leaving to each resource: the
spec's unique tiebreaker is always the final sort key, so pages cannot repeat
rows when the leading column is all nulls; ordering is NULLS LAST in both
directions, since Postgres otherwise floats empty values to the top the moment
the sort direction flips; the scope predicate is a separate conjunct ahead of
every caller filter, so a filter on a scoped column cannot widen it; and a
denied scope is a 403 problem rather than a 200 with an empty list.

No route and no consumer yet; budgets registers against it next. The facet
endpoint's has_more shapes are untouched, and a test pins them so page mode
cannot quietly absorb them.

* fix(proxy): accept the bare filter[field] form in the list framework

Section 5 of the design doc spells equality without an operator bracket
(`?filter[status]=active`, and `/management/v1/keys?filter[team_id]=` in the
sub-resource paragraph); only the other operators carry a second bracket. The
parser only understood `filter[field][op]`, so the canonical spelling came back
as an unknown query parameter.

`filter[field]` now resolves to the field's `eq` operator, which means it still
goes through the declared operator set rather than around it: a field that does
not offer `eq` rejects the shorthand. The allowed-parameter list advertises the
bare spelling for `eq` and the bracketed one for everything else.

Drops two guards from the key parser that could not fire. Operator validation
already rejects every malformed operator, and `field in spec.filters` already
rejects every field nobody declared, so a well-formedness check on top of them
was unreachable; the tests cover the malformed keys directly instead.

* fix(proxy): validate list specs at construction and reject repeated params

Two gaps a review flagged on the list framework.

The page-size cap was only enforced against a supplied page_size, so a spec
whose default_page_size exceeded its max_page_size served more rows than the
resource allows on exactly the request that omits the parameter. A default of
zero was worse: it reached the total_pages division and made the resource 500 on
every request. ListSpec now validates 1 <= default_page_size <= max_page_size
when it is built, so a misconfigured resource fails as it is registered rather
than per request. default_sort is checked against sortable for the same reason;
caller-supplied sort was already validated, but the default never passed through
that path and a typo there reached the ORDER BY clause untouched. Raising is
right here despite the usual model-failures-as-values rule: there is no request
in flight and no caller to answer.

Repeated query parameters silently collapsed to their last value, so
?page=1&page=999 paged from 999 and a repeated sort key quietly won, which is
the same silently-altered-semantics failure the surface already rejects unknown
parameters to avoid. They are now a 400. The check lives in handle_list rather
than build_query_plan because a Mapping[str, str] cannot represent a repeat at
all; the boundary that can see one is the boundary that rejects it. A denied
scope still outranks it, matching every other rejection here.

Also corrects the order_by_sql docstring, which claimed every field reaching it
had been validated against sortable. That held for caller-supplied sort only.

* refactor(proxy): model list predicates as frozen values instead of dicts

The LIT002 budget rejected the framework: building a where-fragment meant a dict
literal per operator, and a dict keyed by a column name chosen at runtime cannot
be frozen into a TypedDict or a dataclass field, so there was no spelling of the
old shape the rule would accept.

Replacing the fragments with a tagged union removes the construction entirely. A
plan's where is now a tuple of frozen Compare / Within / IsNull / AnyOf, matched
exhaustively, and the field name is a value rather than a key. That also retires
the Mapping[str, object] the plan used to carry, which said nothing about what
was inside it and left the fragment shape as a convention two sides had to keep
agreeing on. Scope predicates take the same type, so a resource declares its row
filter in the same vocabulary rather than hand-rolling a backend dict.

where_sql renders a plan for a raw-SQL executor, binding every caller-supplied
value to a numbered placeholder and writing only spec-declared column names into
the statement. It is the counterpart to order_by_sql, which already existed for
the same reason: nulls ordering forces the executor onto raw SQL, so the escaping
and placeholder arithmetic belong in one reviewed place rather than in each
consumer.

Also folds the two remaining mutable builds out of the module (set comprehensions
and Counter to frozenset/tuple, the serialized page to a tuple pydantic coerces),
and lifts the LIKE escaper into common.py so the facet endpoint and the framework
share one copy instead of two that can drift.

No behavioural change to the facet endpoint; its tests, including the one pinning
the escaping, pass untouched.
2026-07-31 09:26:30 -07:00
..
management_v1 feat(proxy): add generic list handler for /management/v1 (#35308) 2026-07-31 09:26:30 -07:00
policy_endpoints style: run black formatter on files from main merge 2026-04-17 13:02:59 -07:00
scim fix(scim): stop provisioning nested group ids as internal users (#34997) 2026-07-29 09:38:17 -07:00
search_endpoints fix(proxy): self-heal startup/reload prisma reads on engine disconnect (#28803) 2026-06-10 20:16:58 +02:00
usage_endpoints fix(proxy): reject user_id=None on non-admin analytics endpoints (cross-tenant disclosure) 2026-04-30 23:54:01 +02:00
test_access_group_endpoints.py update test cases to match new behaviour. The earlier test cases assumed the cache stores a pydantic object 2026-04-28 21:08:46 +00:00
test_access_group_management.py fix(proxy): report when a model write does not survive the post-write reload 2026-07-28 18:52:06 -07:00
test_activity_tenant_scoping.py fix(proxy): guard agent activity owned-fallback against null user_id 2026-05-01 22:04:54 +00:00
test_budget_endpoints.py fix(budget): recompute budget_reset_at when budget_duration changes on /budget/update (#30555) 2026-06-16 14:12:39 -07:00
test_cache_settings_endpoints.py fix(ui): reflect REDIS_* env cache config and stop the UI overwriting the stored password (#34160) 2026-07-21 18:36:26 -07:00
test_callback_management_endpoints.py fix(galileo): use ingest traces API and standard logging payload (#29651) 2026-06-05 09:03:17 -07:00
test_common_daily_activity.py fix(proxy): fall back on empty-string model_group in aggregated usage SQL 2026-07-29 09:59:34 -07:00
test_common_utils.py test(proxy): add regression tests for management_endpoints edge cases (#32976) 2026-07-13 15:05:13 -07:00
test_compliance_endpoints.py fix(proxy): match multi-mode guardrail_mode without false-COMPLIANT (#32832) 2026-07-10 16:41:28 -07:00
test_config_override_endpoints.py fix(audit): label vault POST as updated when DB row exists 2026-05-01 02:44:47 +00:00
test_coordination_redis_endpoints.py feat(proxy): configure the coordination redis independently of the response cache (#32661) 2026-07-10 16:15:59 -07:00
test_cost_tracking_settings.py test(proxy): add regression tests for management_endpoints edge cases (#32976) 2026-07-13 15:05:13 -07:00
test_credential_migration.py feat(proxy): add AES-256-GCM at-rest credential encryption with versioned format and re-encryption migration (#31215) 2026-06-29 20:14:22 +02:00
test_customer_budget.py feat(proxy): type Customer Management response_model for OpenAPI coverage (#31043) 2026-06-30 09:58:01 -07:00
test_customer_endpoints.py refactor(management): move the logs end-user filter onto /management/v1 2026-07-25 23:57:25 -07:00
test_delete_callbacks_endpoint.py style: run black formatter on files from main merge 2026-04-17 13:02:59 -07:00
test_delete_verification_tokens_failed.py style: run black formatter on files from main merge 2026-04-17 13:02:59 -07:00
test_encryption_endpoints.py feat(proxy): add AES-256-GCM at-rest credential encryption with versioned format and re-encryption migration (#31215) 2026-06-29 20:14:22 +02:00
test_entraid_app_roles.py style: run black formatter on files from main merge 2026-04-17 13:02:59 -07:00
test_internal_user_endpoints.py feat(mcp): enforce per-user MCP tool-call entitlements in the auth module (#35146) 2026-07-30 12:06:33 -07:00
test_key_management_endpoints.py fix(proxy): allow /key/update to identify the key by key_alias (#34851) 2026-07-29 09:48:08 -07:00
test_mcp_management_endpoints.py fix(mcp): annotate connected-app reachability on the gateway connect page (#34867) 2026-07-31 05:38:54 +00:00
test_model_management_endpoints.py fix(proxy): reject model writes that corrupt an auto-router pseudo-model 2026-07-28 20:25:07 -07:00
test_org_admin_team_access.py style: run black formatter on files from main merge 2026-04-17 13:02:59 -07:00
test_organization_endpoints.py feat(organization): add RESTful PATCH /v2/organization/{organization_id} (#32350) 2026-07-23 04:53:20 +00:00
test_policy_endpoints.py style: run black formatter on files from main merge 2026-04-17 13:02:59 -07:00
test_project_org_authz.py fix(tests): use canonical litellm_enterprise import path (#27699) 2026-05-12 12:32:57 -07:00
test_router_settings_endpoints.py feat: routing groups ui 2026-05-04 18:09:14 -07:00
test_saml_sso.py feat(proxy): add SAML 2.0 SSO for the admin UI (#31429) 2026-07-24 12:51:28 -07:00
test_tag_management_endpoints.py Fix internal tag usage scoping (#27315) 2026-05-11 10:44:50 -07:00
test_team_callback_endpoints.py encrypt callback_vars in key/team metadata at rest (#27141) 2026-05-23 12:15:44 -07:00
test_team_default_params.py feat(proxy): audit remaining system-wide settings updates (#31754) 2026-06-30 19:32:27 -07:00
test_team_endpoints.py Merge pull request #34266 from BerriAI/litellm_team-model-allowlist-stale-qqg50q 2026-07-29 11:00:38 -07:00
test_team_model_alias_merge.py chore(oss): litellm oss staging 120626 (#30292) 2026-06-12 09:49:25 -07:00
test_tool_management_endpoints.py fix(proxy): roll up tool spend daily instead of scanning SpendLogs 2026-07-25 21:52:58 -07:00
test_ui_sso.py feat(proxy): add SAML 2.0 SSO for the admin UI (#31429) 2026-07-24 12:51:28 -07:00
test_workflow_management_endpoints.py feat(proxy): durable agent workflow run tracking via /v1/workflows/runs (#26793) 2026-04-29 17:12:18 -07:00