mirror of
https://github.com/BerriAI/litellm.git
synced 2026-11-13 08:52:02 +00:00
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
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:
parent
111668467b
commit
d1c2674e6c
2 changed files with 93 additions and 31 deletions
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue