refactor(logs): match ui_view_spend_logs' team-scope clause shape

The scope predicate expanded permitted team ids into an IN list with one
placeholder each, copied from /key/aliases. ui_view_spend_logs, which owns
the same page and the same scoping rules, builds ("user" = $X OR team_id =
ANY($Y::text[])) instead: a single array parameter whatever the team count,
and no placeholder arithmetic to keep in step with the rest of the query.

Same semantics, but the two clauses now read identically, so a future change
to how spend logs are scoped is harder to apply to one and miss in the other.
This commit is contained in:
Yuneng Jiang 2026-07-24 17:39:29 -07:00
parent 2a50b3a087
commit fc278bbf9f
No known key found for this signature in database
2 changed files with 11 additions and 8 deletions

View file

@ -833,9 +833,10 @@ async def _build_end_user_scope_condition(
team_clause: tuple[str, ...] = ()
if permitted_team_ids:
placeholders = ", ".join(f"${len(query_params) + i + 1}" for i in range(len(permitted_team_ids)))
query_params.extend(permitted_team_ids)
team_clause = (f"team_id IN ({placeholders})",)
# = ANY(::text[]) rather than an expanded IN list, matching the clause
# ui_view_spend_logs builds: one parameter whatever the team count.
query_params.append(permitted_team_ids)
team_clause = (f"team_id = ANY(${len(query_params)}::text[])",)
scope_parts = user_clause + team_clause
if not scope_parts:

View file

@ -885,7 +885,7 @@ def test_customer_aliases_applies_no_scope_for_a_proxy_admin(mock_prisma_client,
sql = query_raw.call_args.args[0]
assert '"user" =' not in sql
assert "team_id IN" not in sql
assert "team_id" not in sql
@pytest.mark.parametrize("role", [LitellmUserRoles.INTERNAL_USER, LitellmUserRoles.INTERNAL_USER_VIEW_ONLY])
@ -904,8 +904,10 @@ def test_customer_aliases_scopes_a_team_admin_to_their_own_rows_and_teams(mock_p
assert response.status_code == 200
sql = query_raw.call_args.args[0]
assert '("user" = $3 OR team_id IN ($4, $5))' in sql
assert query_raw.call_args.args[3:6] == ("team-admin-1", "team-a", "team-b")
# Same clause shape ui_view_spend_logs builds, so the two cannot diverge.
assert '("user" = $3 OR team_id = ANY($4::text[]))' in sql
assert query_raw.call_args.args[3] == "team-admin-1"
assert query_raw.call_args.args[4] == ["team-a", "team-b"]
def test_customer_aliases_scopes_a_teamless_user_to_their_own_rows(mock_prisma_client):
@ -923,7 +925,7 @@ def test_customer_aliases_scopes_a_teamless_user_to_their_own_rows(mock_prisma_c
assert response.status_code == 200
sql = query_raw.call_args.args[0]
assert '("user" = $3)' in sql
assert "team_id IN" not in sql
assert "team_id" not in sql
assert query_raw.call_args.args[3] == "solo"
@ -960,7 +962,7 @@ def test_customer_aliases_scopes_when_permitted_team_lookup_fails(mock_prisma_cl
assert response.status_code == 200
sql = query_raw.call_args.args[0]
assert '("user" = $3)' in sql
assert "team_id IN" not in sql
assert "team_id" not in sql
def test_customer_aliases_fetches_one_extra_row_and_trims_it(mock_prisma_client, mock_user_api_key_auth):