fix(proxy): json-encode team budget_limits on /team/new so Prisma accepts multi-window budgets

This commit is contained in:
mubashir1osmani 2026-06-22 10:58:14 -07:00
parent fe39041c06
commit e47e902ce0
2 changed files with 65 additions and 0 deletions

View file

@ -1268,6 +1268,11 @@ async def new_team(
complete_team_data_dict = complete_team_data.model_dump(exclude_none=True)
if complete_team_data_dict.get("budget_limits") is not None:
complete_team_data_dict["budget_limits"] = json.dumps(
complete_team_data_dict["budget_limits"]
)
# Serialize router_settings to JSON (matching key creation pattern)
router_settings_value = getattr(data, "router_settings", None)
router_settings_json = (

View file

@ -461,6 +461,66 @@ async def test_new_team_with_object_permission(mock_db_client, mock_admin_auth):
assert "object_permission" not in team_data
@pytest.mark.asyncio
async def test_new_team_serializes_multi_window_budget_limits(
mock_db_client, mock_admin_auth
):
"""
Regression test for LIT-3896: /team/new must json-encode budget_limits before
passing them to Prisma. The LiteLLM_TeamTable.budget_limits column is `Json?`,
and jsonify_team_object only stringifies dict values, so a raw Python list
reaches Prisma unencoded and the create call 500s.
"""
import json
mock_db_client.jsonify_team_object = lambda db_data: db_data
mock_db_client.get_data = AsyncMock(return_value=None)
mock_db_client.update_data = AsyncMock(return_value=MagicMock())
mock_db_client.db = MagicMock()
mock_db_client.db.litellm_teamtable = MagicMock()
mock_db_client.db.litellm_teamtable.count = AsyncMock(return_value=0)
mock_db_client.db.litellm_teamtable.create = AsyncMock(
return_value=MagicMock(team_id="team-budget-windows")
)
mock_db_client.db.litellm_teamtable.update = AsyncMock(return_value=MagicMock())
mock_db_client.db.litellm_usertable = MagicMock()
mock_db_client.db.litellm_usertable.update = AsyncMock(return_value=MagicMock())
from fastapi import Request
from litellm.models.team import BudgetLimitEntry
from litellm.proxy._types import NewTeamRequest
from litellm.proxy.management_endpoints.team_endpoints import new_team
team_request = NewTeamRequest(
team_alias="multi-window-team",
budget_limits=[
BudgetLimitEntry(budget_duration="1d", max_budget=10.0),
BudgetLimitEntry(budget_duration="30d", max_budget=100.0),
],
)
dummy_request = MagicMock(spec=Request)
await new_team(
data=team_request,
http_request=dummy_request,
user_api_key_dict=mock_admin_auth,
)
created_data = mock_db_client.db.litellm_teamtable.create.call_args.kwargs["data"]
assert isinstance(created_data["budget_limits"], str)
decoded = json.loads(created_data["budget_limits"])
assert isinstance(decoded, list)
assert len(decoded) == 2
assert {w["budget_duration"] for w in decoded} == {"1d", "30d"}
assert {w["max_budget"] for w in decoded} == {10.0, 100.0}
assert all(w.get("reset_at") for w in decoded)
@pytest.mark.asyncio
async def test_new_team_with_mcp_tool_permissions(mock_db_client, mock_admin_auth):
"""