mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-08 03:08:45 +00:00
fix(organization): 422 on negative limits and unparseable budget_duration in v2 update
This commit is contained in:
parent
62087c5d5f
commit
976ff0a785
3 changed files with 54 additions and 2 deletions
|
|
@ -22,7 +22,7 @@ def validate_finite_spend(spend: float | None) -> None:
|
|||
)
|
||||
|
||||
|
||||
def validate_budget_duration(budget_duration: str | None) -> None:
|
||||
def validate_budget_duration(budget_duration: str | None, status_code: int = 400) -> None:
|
||||
"""Reject budget durations that can't be parsed, are non-positive, or
|
||||
overflow date math, so a bad value can't be persisted and later crash the
|
||||
budget reset job.
|
||||
|
|
@ -44,7 +44,7 @@ def validate_budget_duration(budget_duration: str | None) -> None:
|
|||
get_budget_reset_time(budget_duration=budget_duration)
|
||||
except (ValueError, OverflowError):
|
||||
raise HTTPException(
|
||||
status_code=400,
|
||||
status_code=status_code,
|
||||
detail={
|
||||
"error": f"Invalid budget_duration '{budget_duration}'. Use a format like '1h', '24h', '7d', or '30d'."
|
||||
},
|
||||
|
|
|
|||
|
|
@ -41,6 +41,7 @@ from litellm.proxy.management_endpoints.common_daily_activity import get_daily_a
|
|||
from litellm.proxy.management_endpoints.common_utils import (
|
||||
_set_object_metadata_field,
|
||||
_user_has_admin_view,
|
||||
validate_budget_duration,
|
||||
)
|
||||
from litellm.proxy.management_helpers.object_permission_utils import (
|
||||
handle_update_object_permission_common,
|
||||
|
|
@ -807,6 +808,17 @@ async def update_organization_v2(
|
|||
status_code=422,
|
||||
detail={"error": f"soft_budget must be a non-negative finite number. Received: {data.soft_budget}"},
|
||||
)
|
||||
for limit_name, limit_value in (
|
||||
("tpm_limit", data.tpm_limit),
|
||||
("rpm_limit", data.rpm_limit),
|
||||
("max_parallel_requests", data.max_parallel_requests),
|
||||
):
|
||||
if limit_value is not None and limit_value < 0:
|
||||
raise HTTPException(
|
||||
status_code=422,
|
||||
detail={"error": f"{limit_name} must be non-negative. Received: {limit_value}"},
|
||||
)
|
||||
validate_budget_duration(data.budget_duration, status_code=422)
|
||||
if data.model_max_budget:
|
||||
from litellm.proxy.management_endpoints.key_management_endpoints import (
|
||||
validate_model_max_budget,
|
||||
|
|
|
|||
|
|
@ -814,6 +814,46 @@ async def test_v2_rejects_negative_max_budget(monkeypatch):
|
|||
assert "max_budget" in str(exc.value.detail)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.parametrize("field", ["tpm_limit", "rpm_limit", "max_parallel_requests"])
|
||||
async def test_v2_rejects_negative_integer_limits(monkeypatch: pytest.MonkeyPatch, field: str):
|
||||
"""v2 rejects negative tpm/rpm/parallel-request limits with a 422 instead of persisting them to the budget row."""
|
||||
from litellm.proxy._types import LitellmUserRoles, OrganizationUpdateRequestV2, UserAPIKeyAuth
|
||||
from litellm.proxy.management_endpoints.organization_endpoints import update_organization_v2
|
||||
|
||||
monkeypatch.setattr("litellm.proxy.proxy_server.prisma_client", AsyncMock())
|
||||
|
||||
auth = UserAPIKeyAuth(user_role=LitellmUserRoles.PROXY_ADMIN, user_id="admin-1")
|
||||
with pytest.raises(HTTPException) as exc:
|
||||
await update_organization_v2(
|
||||
organization_id="org-1",
|
||||
data=OrganizationUpdateRequestV2.model_validate({field: -1}),
|
||||
user_api_key_dict=auth,
|
||||
)
|
||||
assert exc.value.status_code == 422
|
||||
assert field in str(exc.value.detail)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_v2_rejects_unparseable_budget_duration(monkeypatch: pytest.MonkeyPatch):
|
||||
"""v2 rejects a budget_duration the parser can't read with a 422 instead of persisting it alongside a silent
|
||||
next-midnight fallback reset."""
|
||||
from litellm.proxy._types import LitellmUserRoles, OrganizationUpdateRequestV2, UserAPIKeyAuth
|
||||
from litellm.proxy.management_endpoints.organization_endpoints import update_organization_v2
|
||||
|
||||
monkeypatch.setattr("litellm.proxy.proxy_server.prisma_client", AsyncMock())
|
||||
|
||||
auth = UserAPIKeyAuth(user_role=LitellmUserRoles.PROXY_ADMIN, user_id="admin-1")
|
||||
with pytest.raises(HTTPException) as exc:
|
||||
await update_organization_v2(
|
||||
organization_id="org-1",
|
||||
data=OrganizationUpdateRequestV2.model_validate({"budget_duration": "bogus"}),
|
||||
user_api_key_dict=auth,
|
||||
)
|
||||
assert exc.value.status_code == 422
|
||||
assert "budget_duration" in str(exc.value.detail)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_v2_rejects_caller_without_org_access(monkeypatch):
|
||||
"""v2 runs the real _verify_org_access guard: a non-admin without ORG_ADMIN on the org gets 403 and no write."""
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue