test: tighten shared OAuth and retired Lens startup coverage
Some checks are pending
Cost map pins recheck / rerun-cost-map-guard (push) Waiting to run
LiteLLM Rust DB / db-queries (push) Waiting to run
LiteLLM Rust / rust-lint (push) Waiting to run
LiteLLM Rust / rust-test (push) Waiting to run
LiteLLM Rust / rust-wheel (push) Waiting to run
Terraform Modules / fmt, validate, test (aws) (push) Waiting to run
Terraform Modules / fmt, validate, test (gcp) (push) Waiting to run
Terraform Provider / gofmt, vet, build, test (push) Waiting to run
Terraform Provider / Provider endpoints vs proxy OpenAPI schema (push) Waiting to run

This commit is contained in:
Tin 2026-11-12 16:39:12 -05:00
parent 111668467b
commit d1c2674e6c
2 changed files with 93 additions and 31 deletions

View file

@ -3126,27 +3126,28 @@ async def test_jwt_auto_register_forwards_bound_agent_id():
@pytest.mark.parametrize("route", ("/team/list", "/model/info"))
@pytest.mark.parametrize("jwt_override", (False, True))
@pytest.mark.parametrize("active", (True, False))
async def test_oauth2_info_routes_validate_introspection_request(
route: str, jwt_override: bool, active: bool
) -> None:
async def test_oauth2_info_routes_validate_introspection_request(route: str, jwt_override: bool, active: bool) -> None:
import httpx
import jwt
from litellm.llms.custom_httpx.http_handler import AsyncHTTPHandler
token: Final = (
jwt.encode({"iss": "oauth-fixture", "sub": "fixture-client"}, "fixture-signing-key-for-dispatch-only")
if jwt_override
else "fixture-opaque-token+with/slash="
)
cache: Final = UserApiKeyCache()
handler: Final = JWTHandler()
handler.update_environment(
prisma_client=None,
user_api_key_cache=DualCache(),
user_api_key_cache=cache,
litellm_jwtauth=LiteLLM_JWTAuth(routing_overrides=[JWTRoutingOverride(iss="oauth-fixture", path="oauth2")]),
)
settings: Final = {"enable_oauth2_auth": not jwt_override, "enable_jwt_auth": True, "enforce_user_param": True}
request: Final = Request({"type": "http", "method": "GET", "path": route, "headers": [], "query_string": b""})
async def token_info(outbound: httpx.Request, **kwargs: object) -> httpx.Response:
async def token_info(outbound: httpx.Request) -> httpx.Response:
assert str(outbound.url) == "https://oauth.fixture.test/introspect"
assert outbound.method == "POST"
assert outbound.headers["content-type"] == "application/x-www-form-urlencoded"
@ -3157,32 +3158,35 @@ async def test_oauth2_info_routes_validate_introspection_request(
json={"active": active, "sub": "oauth-user", "role": "proxy_admin"},
)
with (
patch("litellm.proxy.proxy_server.general_settings", settings),
patch("litellm.proxy.proxy_server.jwt_handler", handler),
patch("litellm.proxy.proxy_server.premium_user", True),
patch("litellm.proxy.proxy_server.master_key", "sk-fixture-master"),
patch("litellm.proxy.proxy_server.prisma_client", None),
patch("litellm.proxy.proxy_server.user_api_key_cache", DualCache()),
patch.dict(
os.environ,
{
"OAUTH_TOKEN_INFO_ENDPOINT": "https://oauth.fixture.test/introspect",
"OAUTH_CLIENT_ID": "fixture-client",
"OAUTH_CLIENT_SECRET": "fixture-client-secret",
"OAUTH_USER_ID_FIELD_NAME": "sub",
"OAUTH_USER_ROLE_FIELD_NAME": "role",
},
),
patch("httpx.AsyncClient.send", side_effect=token_info) as network,
):
if active:
identity: Final = await user_api_key_auth(request=request, api_key=f"Bearer {token}")
assert (identity.user_id, identity.user_role) == ("oauth-user", LitellmUserRoles.PROXY_ADMIN)
else:
with pytest.raises(ProxyException, match="Token is not active"):
await user_api_key_auth(request=request, api_key=f"Bearer {token}")
assert network.call_count == 1
network: Final = AsyncMock(wraps=token_info)
client: Final = AsyncHTTPHandler(transport=httpx.MockTransport(network))
async with client.client:
with (
patch("litellm.proxy.proxy_server.general_settings", settings),
patch("litellm.proxy.proxy_server.jwt_handler", handler),
patch("litellm.proxy.proxy_server.premium_user", True),
patch("litellm.proxy.proxy_server.master_key", "sk-fixture-master"),
patch("litellm.proxy.proxy_server.prisma_client", None),
patch("litellm.proxy.proxy_server.user_api_key_cache", cache),
patch.dict(
os.environ,
{
"OAUTH_TOKEN_INFO_ENDPOINT": "https://oauth.fixture.test/introspect",
"OAUTH_CLIENT_ID": "fixture-client",
"OAUTH_CLIENT_SECRET": "fixture-client-secret",
"OAUTH_USER_ID_FIELD_NAME": "sub",
"OAUTH_USER_ROLE_FIELD_NAME": "role",
},
),
patch("litellm.proxy.auth.oauth2_check.get_async_httpx_client", return_value=client),
):
if active:
identity: Final = await user_api_key_auth(request=request, api_key=f"Bearer {token}")
assert (identity.user_id, identity.user_role) == ("oauth-user", LitellmUserRoles.PROXY_ADMIN)
else:
with pytest.raises(ProxyException, match="Token is not active"):
await user_api_key_auth(request=request, api_key=f"Bearer {token}")
assert network.await_count == 1
class TestJWTOAuth2Coexistence:

View file

@ -26,11 +26,13 @@ import subprocess
from collections.abc import AsyncIterator, Awaitable, Callable
from contextlib import asynccontextmanager
from datetime import datetime, timedelta, timezone
from pathlib import Path
from typing import Final
from unittest.mock import AsyncMock, MagicMock, patch
import pytest
from apscheduler.schedulers.asyncio import AsyncIOScheduler
from fastapi import FastAPI
from pydantic import BaseModel
from typing_extensions import TypedDict
@ -53,6 +55,7 @@ from litellm.proxy.proxy_server import (
save_worker_config,
)
from litellm.proxy.utils import PrismaClient
from tests._master_key import MASTER_KEY
from .conftest import normalize
@ -1023,6 +1026,61 @@ async def test_proxy_startup_event_honors_disable_model_info_refresh(
)
@pytest.mark.asyncio
@pytest.mark.parametrize(
"environment_name",
(None, "LITELLM_LENS_URL", "LITELLM_LENS_PUBLIC_URL", "LITELLM_LENS_SERVICE_TOKEN", "LENS_GATEWAY_SECRET"),
ids=("yaml", "url", "public-url", "service-token", "gateway-secret"),
)
async def test_proxy_startup_warns_for_retired_lens_settings_without_disclosing_values(
environment_name: str | None,
tmp_path: Path,
monkeypatch: pytest.MonkeyPatch,
caplog: pytest.LogCaptureFixture,
) -> None:
sentinel: Final = "https://private-lens-config-sentinel.invalid"
secret: Final = "private-lens-secret-sentinel"
tracing_yaml: Final = (
f" tracing:\n store:\n type: lens\n url: {sentinel}\n token: {secret}\n"
if environment_name is None
else ""
)
config_file: Final = tmp_path / "config.yaml"
config_file.write_text(
"model_list: []\ngeneral_settings:\n disable_model_info_refresh: true\n" + tracing_yaml,
encoding="utf-8",
)
environment: Final = (
{environment_name: sentinel if environment_name.endswith("URL") else secret}
if environment_name is not None
else {}
)
monkeypatch.setattr(ps, "proxy_config", ps.ProxyConfig())
with (
patch.dict(
os.environ,
{
"CONFIG_FILE_PATH": str(config_file),
"LITELLM_MASTER_KEY": MASTER_KEY,
"LITELLM_LOCAL_MODEL_COST_MAP": "True",
"PYTHON_DOTENV_DISABLED": "1",
**environment,
},
clear=True,
),
caplog.at_level(logging.WARNING, logger="LiteLLM Proxy"),
):
async with proxy_startup_event(FastAPI()):
assert (
"LiteLLM Proxy",
logging.WARNING,
"The Lens integration has been removed. general_settings.tracing, LITELLM_LENS_* and "
"LENS_GATEWAY_SECRET are ignored. Remove these obsolete settings from your configuration.",
) in caplog.record_tuples
assert sentinel not in caplog.text
assert secret not in caplog.text
@pytest.mark.asyncio
async def test_proxy_shutdown_commits_spend_before_waiting_on_a_slow_logging_task_and_again_after_it() -> None:
"""A worker killed while shutdown waits on a slow logging task keeps the spend it had already counted,