litellm/tests/integration/management/test_team_new.py
devin-ai-integration[bot] 780c504e00
test: migrate tests/proxy_behavior into integration and unit tiers (#45939)
* test: migrate tests/proxy_behavior into integration and unit tiers (partial)

* test(integration): run settings-dependent management tests on an owned proxy

* test(integration): assert stored values in the rewritten management rows

* test(integration): pin sibling team allocations against the org guaranteed throughput cap

* test(integration): keep Prisma clients out of the shared integration conftest import path

---------

Co-authored-by: yuneng <yuneng@berri.ai>
2026-10-11 08:28:42 -07:00

130 lines
5.2 KiB
Python

from typing import Any, Dict
import pytest
from tests.integration._support.actors import Actor
pytestmark = pytest.mark.asyncio(loop_scope="session")
# POST /team/new — actor x org-target matrix (org_target picks the request's
# organization_id: none / ORG_A / ORG_B). Pinned against the role gate, which
# 401s every denial: PROXY_ADMIN always passes; any other caller must name an
# organization_id AND be ORG_ADMIN of that org.
_SCENARIOS = [
("none/proxy_admin", Actor.PROXY_ADMIN, "none", 200),
("none/org_admin", Actor.ORG_ADMIN, "none", 401),
("none/team_admin", Actor.TEAM_ADMIN, "none", 401),
("none/internal_user", Actor.INTERNAL_USER, "none", 401),
("none/owner", Actor.OWNER, "none", 401),
("none/unrelated_same_org", Actor.UNRELATED_SAME_ORG, "none", 401),
("none/cross_org_user", Actor.CROSS_ORG_USER, "none", 401),
("none/service_account", Actor.SERVICE_ACCOUNT, "none", 401),
("none/org_b_admin", Actor.ORG_B_ADMIN, "none", 401),
("org_a/proxy_admin", Actor.PROXY_ADMIN, "org_a", 200),
("org_a/org_admin", Actor.ORG_ADMIN, "org_a", 200),
("org_a/team_admin", Actor.TEAM_ADMIN, "org_a", 401),
("org_a/internal_user", Actor.INTERNAL_USER, "org_a", 401),
("org_a/owner", Actor.OWNER, "org_a", 401),
("org_a/unrelated_same_org", Actor.UNRELATED_SAME_ORG, "org_a", 401),
("org_a/cross_org_user", Actor.CROSS_ORG_USER, "org_a", 401),
("org_a/service_account", Actor.SERVICE_ACCOUNT, "org_a", 401),
("org_a/org_b_admin", Actor.ORG_B_ADMIN, "org_a", 401),
("org_b/proxy_admin", Actor.PROXY_ADMIN, "org_b", 200),
("org_b/org_admin", Actor.ORG_ADMIN, "org_b", 401),
("org_b/team_admin", Actor.TEAM_ADMIN, "org_b", 401),
("org_b/internal_user", Actor.INTERNAL_USER, "org_b", 401),
("org_b/owner", Actor.OWNER, "org_b", 401),
("org_b/unrelated_same_org", Actor.UNRELATED_SAME_ORG, "org_b", 401),
("org_b/cross_org_user", Actor.CROSS_ORG_USER, "org_b", 401),
("org_b/service_account", Actor.SERVICE_ACCOUNT, "org_b", 401),
("org_b/org_b_admin", Actor.ORG_B_ADMIN, "org_b", 200),
]
@pytest.mark.parametrize(
"actor,org_target,expected_status",
[(a, o, s) for (_id, a, o, s) in _SCENARIOS],
ids=[s[0] for s in _SCENARIOS],
)
async def test_team_new_authz_matrix(
actor: Actor,
org_target: str,
expected_status: int,
gateway_client,
db,
resources,
actors,
):
caller = actors.keys[actor]
org_id = {
"none": None,
"org_a": actors.org_a_id,
"org_b": actors.org_b_id,
}[org_target]
body: Dict[str, Any] = {"team_id": resources.prefix, "team_alias": resources.prefix}
if org_id is not None:
body["organization_id"] = org_id
resp = await gateway_client.post(
"/team/new",
headers={"Authorization": f"Bearer {caller.cleartext}"},
json=body,
)
assert resp.status_code == expected_status, f"{actor.value} org={org_target}: {resp.status_code} {resp.text}"
row = await db.db.litellm_teamtable.find_unique(where={"team_id": resources.prefix})
if expected_status == 200:
assert row is not None
assert row.organization_id == org_id
else:
assert row is None, f"{actor.value}: denied but team row leaked"
async def test_team_new_rejects_negative_budget(gateway_client, db, resources, actors):
"""Input-validation pin: max_budget < 0 is a 400, no row created."""
resp = await gateway_client.post(
"/team/new",
headers={"Authorization": f"Bearer {actors.keys[Actor.PROXY_ADMIN].cleartext}"},
json={"team_id": resources.prefix, "max_budget": -1},
)
assert resp.status_code == 400, resp.text
row = await db.db.litellm_teamtable.find_unique(where={"team_id": resources.prefix})
assert row is None
async def test_team_new_rejects_duplicate_team_id(gateway_client, db, resources, actors):
"""Input-validation pin: a colliding team_id is a 400 on the second call."""
seeder = actors.keys[Actor.PROXY_ADMIN].cleartext
first = await gateway_client.post(
"/team/new",
headers={"Authorization": f"Bearer {seeder}"},
json={"team_id": resources.prefix, "team_alias": resources.prefix},
)
assert first.status_code == 200, first.text
second = await gateway_client.post(
"/team/new",
headers={"Authorization": f"Bearer {seeder}"},
json={"team_id": resources.prefix, "team_alias": resources.prefix},
)
assert second.status_code == 400, second.text
async def test_team_new_unknown_organization_is_400(gateway_client, db, resources, actors):
"""A /team/new with an organization_id that does not exist fails 400:
OrganizationNotFoundError is routed into the handler's own
'Organization not found' guard instead of escaping as a 500."""
resp = await gateway_client.post(
"/team/new",
headers={"Authorization": f"Bearer {actors.keys[Actor.PROXY_ADMIN].cleartext}"},
json={
"team_id": resources.prefix,
"organization_id": resources.tag("no-such-org"),
},
)
assert resp.status_code == 400, resp.text
assert "Organization not found" in resp.text
row = await db.db.litellm_teamtable.find_unique(where={"team_id": resources.prefix})
assert row is None