Merge pull request #26362 from BerriAI/litellm_fix_proxy_test_master_key_leak
[Fix] Tests - Proxy: Isolate master_key/prisma_client module globals between tests
This commit is contained in:
commit
09f0a3380f
@ -65,12 +65,18 @@ _GIT_SUBDIR_SOURCE = {
|
||||
}
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def _patch_proxy_globals(monkeypatch):
|
||||
"""Scope prisma_client/master_key mutations to each test via monkeypatch."""
|
||||
monkeypatch.setattr(
|
||||
litellm.proxy.proxy_server, "prisma_client", _make_mock_prisma()
|
||||
)
|
||||
monkeypatch.setattr(litellm.proxy.proxy_server, "master_key", "sk-1234")
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_register_plugin_git_subdir_success():
|
||||
"""git-subdir with both url and path fields registers successfully."""
|
||||
setattr(litellm.proxy.proxy_server, "prisma_client", _make_mock_prisma())
|
||||
setattr(litellm.proxy.proxy_server, "master_key", "sk-1234")
|
||||
|
||||
request = RegisterPluginRequest(
|
||||
name="my-monorepo-plugin", source=_GIT_SUBDIR_SOURCE
|
||||
)
|
||||
@ -86,9 +92,6 @@ async def test_register_plugin_git_subdir_success():
|
||||
@pytest.mark.asyncio
|
||||
async def test_register_plugin_git_subdir_update():
|
||||
"""Registering the same git-subdir plugin twice returns action=updated."""
|
||||
setattr(litellm.proxy.proxy_server, "prisma_client", _make_mock_prisma())
|
||||
setattr(litellm.proxy.proxy_server, "master_key", "sk-1234")
|
||||
|
||||
request = RegisterPluginRequest(
|
||||
name="my-monorepo-plugin", source=_GIT_SUBDIR_SOURCE, version="1.0.0"
|
||||
)
|
||||
@ -106,9 +109,6 @@ async def test_register_plugin_git_subdir_update():
|
||||
@pytest.mark.asyncio
|
||||
async def test_register_plugin_git_subdir_missing_url():
|
||||
"""git-subdir without url field raises HTTP 400."""
|
||||
setattr(litellm.proxy.proxy_server, "prisma_client", _make_mock_prisma())
|
||||
setattr(litellm.proxy.proxy_server, "master_key", "sk-1234")
|
||||
|
||||
request = RegisterPluginRequest(
|
||||
name="bad-plugin",
|
||||
source={"source": "git-subdir", "path": "plugins/my-plugin"},
|
||||
@ -124,9 +124,6 @@ async def test_register_plugin_git_subdir_missing_url():
|
||||
@pytest.mark.asyncio
|
||||
async def test_register_plugin_git_subdir_empty_url():
|
||||
"""git-subdir with empty url raises HTTP 400."""
|
||||
setattr(litellm.proxy.proxy_server, "prisma_client", _make_mock_prisma())
|
||||
setattr(litellm.proxy.proxy_server, "master_key", "sk-1234")
|
||||
|
||||
request = RegisterPluginRequest(
|
||||
name="bad-plugin",
|
||||
source={"source": "git-subdir", "url": "", "path": "plugins/my-plugin"},
|
||||
@ -142,9 +139,6 @@ async def test_register_plugin_git_subdir_empty_url():
|
||||
@pytest.mark.asyncio
|
||||
async def test_register_plugin_git_subdir_missing_path():
|
||||
"""git-subdir without path field raises HTTP 400."""
|
||||
setattr(litellm.proxy.proxy_server, "prisma_client", _make_mock_prisma())
|
||||
setattr(litellm.proxy.proxy_server, "master_key", "sk-1234")
|
||||
|
||||
request = RegisterPluginRequest(
|
||||
name="bad-plugin",
|
||||
source={"source": "git-subdir", "url": "https://github.com/org/monorepo.git"},
|
||||
@ -160,9 +154,6 @@ async def test_register_plugin_git_subdir_missing_path():
|
||||
@pytest.mark.asyncio
|
||||
async def test_register_plugin_git_subdir_empty_path():
|
||||
"""git-subdir with empty path raises HTTP 400."""
|
||||
setattr(litellm.proxy.proxy_server, "prisma_client", _make_mock_prisma())
|
||||
setattr(litellm.proxy.proxy_server, "master_key", "sk-1234")
|
||||
|
||||
request = RegisterPluginRequest(
|
||||
name="bad-plugin",
|
||||
source={
|
||||
@ -182,9 +173,6 @@ async def test_register_plugin_git_subdir_empty_path():
|
||||
@pytest.mark.asyncio
|
||||
async def test_register_plugin_git_subdir_path_traversal():
|
||||
"""git-subdir with path traversal segments raises HTTP 400."""
|
||||
setattr(litellm.proxy.proxy_server, "prisma_client", _make_mock_prisma())
|
||||
setattr(litellm.proxy.proxy_server, "master_key", "sk-1234")
|
||||
|
||||
for bad_path in [
|
||||
"../../etc/passwd",
|
||||
"../secrets",
|
||||
@ -213,9 +201,6 @@ async def test_register_plugin_git_subdir_path_traversal():
|
||||
@pytest.mark.asyncio
|
||||
async def test_register_plugin_unknown_source_type():
|
||||
"""Unknown source type raises HTTP 400 listing all valid types."""
|
||||
setattr(litellm.proxy.proxy_server, "prisma_client", _make_mock_prisma())
|
||||
setattr(litellm.proxy.proxy_server, "master_key", "sk-1234")
|
||||
|
||||
request = RegisterPluginRequest(
|
||||
name="bad-plugin",
|
||||
source={"source": "ftp", "url": "ftp://example.com/repo"},
|
||||
|
||||
@ -15,6 +15,40 @@ import yaml
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
|
||||
_PROXY_MODULE_GLOBALS_TO_ISOLATE = (
|
||||
"master_key",
|
||||
"prisma_client",
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def _isolate_proxy_module_globals():
|
||||
"""
|
||||
Snapshot and restore module-level globals on litellm.proxy.proxy_server
|
||||
that tests sometimes mutate via raw setattr (not monkeypatch).
|
||||
|
||||
Without this, a leaked value — e.g. master_key set by a sibling test —
|
||||
flips the auth short-circuit in user_api_key_auth and causes unrelated
|
||||
tests in the same xdist worker to return 401 instead of 200.
|
||||
"""
|
||||
from litellm.proxy import proxy_server
|
||||
|
||||
sentinel = object()
|
||||
snapshot = {
|
||||
name: getattr(proxy_server, name, sentinel)
|
||||
for name in _PROXY_MODULE_GLOBALS_TO_ISOLATE
|
||||
}
|
||||
try:
|
||||
yield
|
||||
finally:
|
||||
for name, value in snapshot.items():
|
||||
if value is sentinel:
|
||||
if hasattr(proxy_server, name):
|
||||
delattr(proxy_server, name)
|
||||
else:
|
||||
setattr(proxy_server, name, value)
|
||||
|
||||
|
||||
def build_cache_config(enable_cache: bool = True) -> Optional[Dict]:
|
||||
"""
|
||||
Build Redis cache configuration from environment variables.
|
||||
|
||||
@ -113,19 +113,11 @@ def test_decode_realtime_token_payload_ephemeral_key_not_string():
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def proxy_app():
|
||||
def proxy_app(monkeypatch):
|
||||
from litellm.proxy import proxy_server
|
||||
|
||||
# master_key is a module-global — restore it on teardown so this fixture
|
||||
# doesn't leak state into unrelated tests that share the same xdist worker
|
||||
# (e.g. tests that assume master_key is None and send unauthenticated
|
||||
# requests to the shared FastAPI app).
|
||||
original_master_key = proxy_server.master_key
|
||||
proxy_server.master_key = "sk-test-master-key"
|
||||
try:
|
||||
yield proxy_server.app
|
||||
finally:
|
||||
proxy_server.master_key = original_master_key
|
||||
monkeypatch.setattr(proxy_server, "master_key", "sk-test-master-key")
|
||||
return proxy_server.app
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
@ -284,10 +276,6 @@ async def test_realtime_calls_success_with_valid_encrypted_token(
|
||||
mock_pre_call_hook,
|
||||
):
|
||||
"""POST /v1/realtime/calls returns 201 with valid encrypted token from client_secrets."""
|
||||
from litellm.proxy import proxy_server
|
||||
|
||||
proxy_server.master_key = "sk-test-master-key"
|
||||
|
||||
# Build a valid encrypted token (same format as client_secrets returns)
|
||||
future_expires_at = int(time.time()) + 3600
|
||||
token_payload = _encode_realtime_token_payload(
|
||||
|
||||
Loading…
Reference in New Issue
Block a user