litellm/tests/e2e/other/other_client.py
ryan-crabbe-berri 2085a37b82 test(e2e): issue the JWT suite's tokens from a real Keycloak realm
The suite used to mint its own RS256 tokens from a stand-in issuer, which
could only ever prove the proxy agreed with the tests: the claims were
whatever the tests chose to sign. Every JWT bug worth catching lives in the
shape of what an identity provider really emits, so the suite now runs
against Keycloak (realm in idp_realm.json), provisions a group and a user per
test through its admin API, and signs in through the direct-access grant.

That changes what the tokens look like: sub is Keycloak's opaque user uuid
rather than a friendly name, groups arrives from a protocol mapper, aud is
the IdP's own audience, and the JWKS carries an encryption key beside the
signing key so the proxy has to select on kid. The expiry case now takes a
one-second token from a second client in the realm and waits for it to lapse
instead of forging a stale exp.

The proxy config the suite needs is unchanged. CI runs it against a Keycloak
deployed beside the ephemeral stack, which lives in the releaser repo.

Claude-Session: https://claude.ai/code/session_01EX13mWex6RaBo9PYnkAtFW
2026-09-09 16:56:14 -07:00

82 lines
3.1 KiB
Python

"""Client for the `other` holding-pen suite: the auth gate (master key vs an
invalid key on an admin route), JWT auth against the suite's Keycloak realm
(idp.py), and the process-lifecycle health probes (liveness, public readiness,
authenticated readiness diagnostics).
Holds the shared ProxyClient so `resources` / `scoped_key` still clean up, and
adds only the routes these behaviors need. The health probes deliberately send
no auth header (public routes), so they go through the transport with an empty
headers model rather than a bearer. JWT tests reach the identity provider
through `idp`, which provisions identities and mints tokens through Keycloak's
own endpoints, so no test ever holds a signing key.
"""
from __future__ import annotations
from dataclasses import dataclass
from e2e_http import NoBody, ProbeResult, Result
from idp import Keycloak, keycloak_from_env
from models import (
ReadinessDetailsResponse,
ReadinessResponse,
UserListParams,
UserListResponse,
)
from proxy_client import ProxyClient
@dataclass(frozen=True, slots=True)
class OtherClient:
proxy: ProxyClient
@property
def idp(self) -> Keycloak:
"""Resolved per use, so the suite's non-JWT tests never need the IdP env."""
return keycloak_from_env()
def liveness(self) -> ProbeResult:
"""GET /health/liveliness. Unauthenticated; the probe returns status +
raw body so the test can assert the worker reports itself alive."""
return self.proxy.transport.probe("/health/liveliness", params=NoBody())
def readiness_public(self) -> Result[ReadinessResponse]:
"""GET /health/readiness with no credential at all, proving the probe is
safe to expose to an unauthenticated load balancer."""
return self.proxy.transport.get(
"/health/readiness",
headers=NoBody(),
params=NoBody(),
response_type=ReadinessResponse,
)
def readiness_details(self, key: str) -> Result[ReadinessDetailsResponse]:
return self.proxy.transport.get(
"/health/readiness/details",
headers=self.proxy.transport.bearer(key),
params=NoBody(),
response_type=ReadinessDetailsResponse,
)
def readiness_details_unauthenticated(self) -> Result[ReadinessDetailsResponse]:
return self.proxy.transport.get(
"/health/readiness/details",
headers=NoBody(),
params=NoBody(),
response_type=ReadinessDetailsResponse,
)
def list_users_as(self, key: str) -> Result[UserListResponse]:
"""GET /user/list under `key`. Admin-only, so it doubles as the master
key's authorization proof: the master key (proxy admin) reads it, a
non-matching key is rejected before it ever reaches the handler."""
return self.proxy.transport.get(
"/user/list",
headers=self.proxy.transport.bearer(key),
params=UserListParams(user_ids="e2e-test-user"),
response_type=UserListResponse,
)
def build_client(proxy: ProxyClient) -> OtherClient:
return OtherClient(proxy=proxy)