mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-26 01:12:21 +00:00
test(proxy_behavior): trim debug diagnostics, restore default max-failures
Followup to the CI-bring-up sequence: now that the suite is green in CI
(130 → 129 tests after this trim; 156s wall-time on ubuntu-latest), drop
the diagnostic noise left over from debugging the master_key wipe:
* Rename ``test_aaa_world_seed.py`` back to ``test_world_seed.py`` —
no longer needs to run first.
* Remove ``test_auth_resolver_returns_correct_user_id_and_role`` —
that test reached into private auth helpers to localize the bug
between the DB and ``UserAPIKeyAuth``; it has served its purpose
and isn't HTTP-boundary.
* Keep ``test_proxy_admin_actor_can_create_keys_for_others`` (without
the failure-time dump) — it's a real authz contract that pins the
PROXY_ADMIN bypass on /key/generate, and would catch a regression
of the same conftest interaction this sequence revealed.
* Drop the workflow's ``max-failures: 200`` override — that was a
debug aid for seeing the full failure surface in CI. Default of 10
is right for a stable suite.
This commit is contained in:
parent
5f038196a0
commit
4c66635ee5
3 changed files with 66 additions and 156 deletions
|
|
@ -29,10 +29,6 @@ jobs:
|
|||
# so the cost of disabling parallelism here is negligible.
|
||||
workers: 0
|
||||
reruns: 0
|
||||
# Don't abort early — first CI runs need the full failure surface so we
|
||||
# can correlate setup-helper failures (one bad fixture cascades to N
|
||||
# tests) vs. real per-scenario failures. Will tighten back down later.
|
||||
max-failures: 200
|
||||
enable-postgres: true
|
||||
artifact-name: proxy-mgmt-behavior
|
||||
timeout-minutes: 15
|
||||
|
|
|
|||
|
|
@ -1,152 +0,0 @@
|
|||
"""Slice 4 smoke: every seeded actor key can authenticate and call /key/info on itself.
|
||||
|
||||
This is the minimum proof that the world is reachable via the real auth stack —
|
||||
the real ``user_api_key_auth`` dependency hashes the cleartext token from the
|
||||
Bearer header, looks it up, resolves the user/role, and the handler returns the
|
||||
key info row.
|
||||
|
||||
If any actor 401/403s here, the seed is wrong (user_role mismatch, scope
|
||||
fields, etc.) before we scale out to the matrix in Slices 7–12.
|
||||
"""
|
||||
|
||||
import pytest
|
||||
|
||||
from .actors import Actor
|
||||
|
||||
pytestmark = pytest.mark.asyncio(loop_scope="session")
|
||||
|
||||
|
||||
@pytest.mark.parametrize("actor", list(Actor), ids=[a.value for a in Actor])
|
||||
async def test_each_actor_can_self_info(actor, proxy_client, world):
|
||||
seeded = world.keys[actor]
|
||||
resp = await proxy_client.get(
|
||||
"/key/info",
|
||||
headers={"Authorization": f"Bearer {seeded.cleartext}"},
|
||||
)
|
||||
assert resp.status_code == 200, f"{actor.value}: {resp.status_code} {resp.text}"
|
||||
body = resp.json()
|
||||
# /key/info returns {"key": <hashed token>, "info": <row dict with `token` popped>}.
|
||||
assert body.get("key") == seeded.hashed, (
|
||||
f"{actor.value}: /key/info returned the wrong key "
|
||||
f"(got {body.get('key')!r}, expected {seeded.hashed!r})"
|
||||
)
|
||||
info = body["info"]
|
||||
assert info.get("user_id") == seeded.user_id, (
|
||||
f"{actor.value}: /key/info returned the wrong user_id "
|
||||
f"(got {info.get('user_id')!r}, expected {seeded.user_id!r})"
|
||||
)
|
||||
|
||||
|
||||
async def test_auth_resolver_returns_correct_user_id_and_role(
|
||||
proxy_client, prisma, world
|
||||
):
|
||||
"""Diagnostic: call the auth-resolver primitives directly with the seeded
|
||||
PROXY_ADMIN actor's hashed token, dump every intermediate so we can see
|
||||
where in the chain user_id / user_role get dropped between the DB and
|
||||
UserAPIKeyAuth."""
|
||||
from litellm.proxy.auth.auth_checks import (
|
||||
get_key_object,
|
||||
get_user_object,
|
||||
_is_user_proxy_admin,
|
||||
_get_user_role,
|
||||
)
|
||||
from litellm.proxy.proxy_server import user_api_key_cache
|
||||
|
||||
seeder = world.keys[Actor.PROXY_ADMIN]
|
||||
|
||||
# 1. Raw prisma get_data → LiteLLM_VerificationTokenView
|
||||
raw_view = await prisma.get_data(token=seeder.hashed, table_name="combined_view")
|
||||
raw_view_user_id = getattr(raw_view, "user_id", "<no attr>")
|
||||
|
||||
# 2. get_key_object (cache miss → DB)
|
||||
key_obj = await get_key_object(
|
||||
hashed_token=seeder.hashed,
|
||||
prisma_client=prisma,
|
||||
user_api_key_cache=user_api_key_cache,
|
||||
)
|
||||
key_user_id = getattr(key_obj, "user_id", "<no attr>")
|
||||
key_user_role = getattr(key_obj, "user_role", "<no attr>")
|
||||
|
||||
# 3. get_user_object for the resolved user_id
|
||||
user_obj = None
|
||||
if key_user_id and key_user_id != "<no attr>":
|
||||
try:
|
||||
user_obj = await get_user_object(
|
||||
user_id=key_user_id,
|
||||
prisma_client=prisma,
|
||||
user_api_key_cache=user_api_key_cache,
|
||||
user_id_upsert=False,
|
||||
)
|
||||
except Exception as exc:
|
||||
user_obj = f"<exc: {exc!r}>"
|
||||
|
||||
is_admin = (
|
||||
_is_user_proxy_admin(user_obj=user_obj)
|
||||
if not isinstance(user_obj, str)
|
||||
else "<n/a>"
|
||||
)
|
||||
resolved_role = (
|
||||
_get_user_role(user_obj=user_obj) if not isinstance(user_obj, str) else "<n/a>"
|
||||
)
|
||||
|
||||
diagnostic = (
|
||||
f"\n expected seed.user_id : {seeder.user_id!r}"
|
||||
f"\n raw_view.user_id : {raw_view_user_id!r}"
|
||||
f"\n key_obj.user_id : {key_user_id!r}"
|
||||
f"\n key_obj.user_role : {key_user_role!r}"
|
||||
f"\n user_obj.user_role : {getattr(user_obj, 'user_role', user_obj)!r}"
|
||||
f"\n _is_user_proxy_admin : {is_admin!r}"
|
||||
f"\n _get_user_role : {resolved_role!r}"
|
||||
)
|
||||
print(f"AUTH RESOLVER DIAGNOSTIC:{diagnostic}")
|
||||
|
||||
assert (
|
||||
key_user_id == seeder.user_id
|
||||
), f"get_key_object dropped user_id: {diagnostic}"
|
||||
assert (
|
||||
is_admin is True
|
||||
), f"_is_user_proxy_admin returned False for the seeded PROXY_ADMIN: {diagnostic}"
|
||||
|
||||
|
||||
async def test_proxy_admin_actor_can_create_keys_for_others(
|
||||
proxy_client, prisma, world
|
||||
):
|
||||
"""Diagnostic: the seeded PROXY_ADMIN actor must be able to /key/generate
|
||||
a key for another user. If this fails, the user_role is not propagating
|
||||
through user_api_key_auth → the actor's auth context disagrees with the
|
||||
DB row, and the cause is elsewhere in the auth stack (not the seed).
|
||||
|
||||
On failure we also probe the underlying state so the CI log tells us
|
||||
exactly which surface returned the wrong shape: the raw token row, the
|
||||
user row, and the combined view (what the auth resolver consumes)."""
|
||||
seeder = world.keys[Actor.PROXY_ADMIN]
|
||||
target_user_id = world.keys[Actor.OWNER].user_id
|
||||
|
||||
resp = await proxy_client.post(
|
||||
"/key/generate",
|
||||
headers={"Authorization": f"Bearer {seeder.cleartext}"},
|
||||
json={"key_alias": "diag-proxy-admin-seeder", "user_id": target_user_id},
|
||||
)
|
||||
if resp.status_code != 200:
|
||||
token_row = await prisma.db.litellm_verificationtoken.find_unique(
|
||||
where={"token": seeder.hashed}
|
||||
)
|
||||
user_row = await prisma.db.litellm_usertable.find_unique(
|
||||
where={"user_id": seeder.user_id}
|
||||
)
|
||||
# View columns: the auth resolver reads from this view, then does a
|
||||
# separate user-table lookup keyed off the view's user_id to populate
|
||||
# user_role. So we want both surfaces independently.
|
||||
view_rows = await prisma.db.query_raw(
|
||||
"SELECT user_id, team_id, organization_id "
|
||||
'FROM "LiteLLM_VerificationTokenView" WHERE token = $1',
|
||||
seeder.hashed,
|
||||
)
|
||||
pytest.fail(
|
||||
f"PROXY_ADMIN-seeded actor can't create keys for others: "
|
||||
f"{resp.status_code} {resp.text}\n"
|
||||
f" seeder user_id (expected): {seeder.user_id}\n"
|
||||
f" token row.user_id : {getattr(token_row, 'user_id', '<missing>')!r}\n"
|
||||
f" user row.user_role : {getattr(user_row, 'user_role', '<missing>')!r}\n"
|
||||
f" view row : {view_rows!r}"
|
||||
)
|
||||
66
tests/proxy_behavior/management/test_world_seed.py
Normal file
66
tests/proxy_behavior/management/test_world_seed.py
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
"""Slice 4 smoke: every seeded actor key can authenticate and call /key/info on itself.
|
||||
|
||||
Minimal proof that the world is reachable via the real auth stack — the real
|
||||
``user_api_key_auth`` dependency hashes the cleartext token from the Bearer
|
||||
header, looks it up, resolves the user / role, and the handler returns the
|
||||
key info row.
|
||||
|
||||
If any actor 401/403s here, the seed is wrong (user_role mismatch, scope
|
||||
fields, etc.) before we scale out to the matrix in Slices 7–12.
|
||||
|
||||
Also pins the PROXY_ADMIN bypass contract for ``/key/generate`` so we don't
|
||||
regress on the master-key / lifespan env-var interaction that surfaced
|
||||
during CI bring-up (see the conftest's ``LITELLM_MASTER_KEY`` note for the
|
||||
underlying mechanism).
|
||||
"""
|
||||
|
||||
import pytest
|
||||
|
||||
from .actors import Actor
|
||||
|
||||
pytestmark = pytest.mark.asyncio(loop_scope="session")
|
||||
|
||||
|
||||
@pytest.mark.parametrize("actor", list(Actor), ids=[a.value for a in Actor])
|
||||
async def test_each_actor_can_self_info(actor, proxy_client, world):
|
||||
seeded = world.keys[actor]
|
||||
resp = await proxy_client.get(
|
||||
"/key/info",
|
||||
headers={"Authorization": f"Bearer {seeded.cleartext}"},
|
||||
)
|
||||
assert resp.status_code == 200, f"{actor.value}: {resp.status_code} {resp.text}"
|
||||
body = resp.json()
|
||||
# /key/info returns {"key": <hashed token>, "info": <row dict with `token` popped>}.
|
||||
assert body.get("key") == seeded.hashed, (
|
||||
f"{actor.value}: /key/info returned the wrong key "
|
||||
f"(got {body.get('key')!r}, expected {seeded.hashed!r})"
|
||||
)
|
||||
info = body["info"]
|
||||
assert info.get("user_id") == seeded.user_id, (
|
||||
f"{actor.value}: /key/info returned the wrong user_id "
|
||||
f"(got {info.get('user_id')!r}, expected {seeded.user_id!r})"
|
||||
)
|
||||
|
||||
|
||||
async def test_proxy_admin_actor_can_create_keys_for_others(proxy_client, world):
|
||||
"""Pins the PROXY_ADMIN bypass for /key/generate's user_id-mismatch gate.
|
||||
|
||||
The seeded PROXY_ADMIN actor must be able to /key/generate a key with an
|
||||
explicit ``user_id`` belonging to a different user. The bypass relies on
|
||||
user_api_key_auth resolving user_role=PROXY_ADMIN from the user-table row
|
||||
keyed off the verification token's user_id. If any link in that chain
|
||||
breaks, this test surfaces it directly rather than as cascading failures
|
||||
in the write matrices that depend on the same setup helper.
|
||||
"""
|
||||
seeder = world.keys[Actor.PROXY_ADMIN]
|
||||
target_user_id = world.keys[Actor.OWNER].user_id
|
||||
|
||||
resp = await proxy_client.post(
|
||||
"/key/generate",
|
||||
headers={"Authorization": f"Bearer {seeder.cleartext}"},
|
||||
json={"key_alias": "smoke-proxy-admin-bypass", "user_id": target_user_id},
|
||||
)
|
||||
assert resp.status_code == 200, (
|
||||
f"PROXY_ADMIN-seeded actor can't create keys for others: "
|
||||
f"{resp.status_code} {resp.text}"
|
||||
)
|
||||
Loading…
Add table
Reference in a new issue