mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-06 08:16:43 +00:00
fix(proxy): include litellm_model_table in GET /v2/team/list (#39045)
* fix(proxy): include litellm_model_table in GET /v2/team/list GET /v2/team/list built its find_many queries without joining the LiteLLM_ModelTable relation, so litellm_model_table (and the model_aliases it carries) always read back as null there, same bug class as GH #26312 which PR #33047 fixed on /team/info and /team/list but never touched this endpoint. * fix(test): assert observable output, not mock calls, in v2 team list test The test-quality gate flagged the regression test for asserting on find_many's call args instead of what the caller gets back. Rewritten so the fake find_many only attaches litellm_model_table when its own include kwarg asks for it, so the assertions are on the response. * fix(proxy): drop invalid litellm_model_table include on deleted-team query Greptile caught that LiteLLM_DeletedTeamTable has no litellm_model_table relation in the Prisma schema, so passing that include on the deleted-team find_many raised UnknownRelationalFieldError against a real database on every GET /v2/team/list?status=deleted call. Confirmed live against Postgres. Scope the fix to the active-team branch only, where the relation exists; update the test to reflect that and assert the deleted branch no longer requests it.
This commit is contained in:
parent
db46973ec4
commit
b11f0bcb92
2 changed files with 89 additions and 0 deletions
|
|
@ -5148,6 +5148,7 @@ async def list_team_v2(
|
|||
|
||||
# Get teams with pagination
|
||||
if use_deleted_table:
|
||||
# LiteLLM_DeletedTeamTable has no litellm_model_table relation, unlike below
|
||||
teams = await _deleted_team_db(prisma_client).find_many(
|
||||
where=where_conditions,
|
||||
skip=skip,
|
||||
|
|
@ -5162,6 +5163,7 @@ async def list_team_v2(
|
|||
skip=skip,
|
||||
take=page_size,
|
||||
order=order_by if order_by else {"created_at": "desc"}, # Default sort
|
||||
include=_INCLUDE_MODEL_TABLE,
|
||||
)
|
||||
# Get total count for pagination
|
||||
total_count = await _team_db(prisma_client).count(where=where_conditions)
|
||||
|
|
|
|||
|
|
@ -3741,6 +3741,93 @@ async def test_list_team_v2_with_status_deleted():
|
|||
assert len(result["teams"]) == 2
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_list_team_v2_includes_litellm_model_table():
|
||||
"""
|
||||
Regression test for GH #26312: GET /v2/team/list must eagerly load the
|
||||
litellm_model_table relation for active teams, same as /team/info and
|
||||
/team/list, or a team's model_aliases always read back as null from this
|
||||
endpoint. Deleted teams are excluded: LiteLLM_DeletedTeamTable has no such
|
||||
relation in the Prisma schema, so requesting it there raises
|
||||
UnknownRelationalFieldError against a real database.
|
||||
|
||||
The fake find_many below only attaches litellm_model_table when its own
|
||||
`include` kwarg actually asks for the relation, so the assertions below
|
||||
are on what the caller gets back, not on how find_many was called.
|
||||
"""
|
||||
from unittest.mock import AsyncMock, Mock, patch
|
||||
|
||||
from fastapi import Request
|
||||
|
||||
from litellm.proxy._types import LitellmUserRoles, UserAPIKeyAuth
|
||||
from litellm.proxy.management_endpoints.team_endpoints import list_team_v2
|
||||
|
||||
mock_request = Mock(spec=Request)
|
||||
mock_user_api_key_dict_admin = UserAPIKeyAuth(
|
||||
user_role=LitellmUserRoles.PROXY_ADMIN,
|
||||
user_id="admin_user_123",
|
||||
)
|
||||
|
||||
def _team_row(team_id: str, include) -> Mock:
|
||||
model_table = (
|
||||
{
|
||||
"id": 1,
|
||||
"model_aliases": {"my-fast-model": "fake-model"},
|
||||
"created_by": "u",
|
||||
"updated_by": "u",
|
||||
"team": None,
|
||||
}
|
||||
if (include or {}).get("litellm_model_table")
|
||||
else None
|
||||
)
|
||||
return Mock(
|
||||
team_id=team_id,
|
||||
model_dump=lambda: {
|
||||
"team_id": team_id,
|
||||
"team_alias": "t",
|
||||
"litellm_model_table": model_table,
|
||||
},
|
||||
)
|
||||
|
||||
with patch("litellm.proxy.proxy_server.prisma_client") as mock_prisma_client: # test-quality-ok: this file's DB-mock convention
|
||||
mock_db = Mock()
|
||||
mock_prisma_client.db = mock_db
|
||||
|
||||
mock_db.litellm_teamtable.find_many = AsyncMock(
|
||||
side_effect=lambda **kw: [_team_row("team_1", kw.get("include"))]
|
||||
)
|
||||
mock_db.litellm_teamtable.count = AsyncMock(return_value=1)
|
||||
mock_db.litellm_verificationtoken.group_by = AsyncMock(return_value=[])
|
||||
|
||||
result = await list_team_v2(
|
||||
http_request=mock_request,
|
||||
user_id=None,
|
||||
user_api_key_dict=mock_user_api_key_dict_admin,
|
||||
page=1,
|
||||
page_size=10,
|
||||
status=None,
|
||||
)
|
||||
|
||||
assert result["teams"][0].litellm_model_table is not None
|
||||
assert result["teams"][0].litellm_model_table.model_aliases == {"my-fast-model": "fake-model"}
|
||||
|
||||
mock_db.litellm_deletedteamtable.find_many = AsyncMock(
|
||||
side_effect=lambda **kw: [_team_row("team_2", kw.get("include"))]
|
||||
)
|
||||
mock_db.litellm_deletedteamtable.count = AsyncMock(return_value=1)
|
||||
|
||||
await list_team_v2(
|
||||
http_request=mock_request,
|
||||
user_id=None,
|
||||
user_api_key_dict=mock_user_api_key_dict_admin,
|
||||
page=1,
|
||||
page_size=10,
|
||||
status="deleted",
|
||||
)
|
||||
|
||||
assert "include" not in mock_db.litellm_deletedteamtable.find_many.call_args.kwargs
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_list_team_v2_org_admin_sees_org_teams():
|
||||
"""
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue