diff --git a/tests/test_litellm/proxy/anthropic_endpoints/test_claude_code_marketplace.py b/tests/test_litellm/proxy/anthropic_endpoints/test_claude_code_marketplace.py index 5511daf1b6..1bdba16612 100644 --- a/tests/test_litellm/proxy/anthropic_endpoints/test_claude_code_marketplace.py +++ b/tests/test_litellm/proxy/anthropic_endpoints/test_claude_code_marketplace.py @@ -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"}, diff --git a/tests/test_litellm/proxy/conftest.py b/tests/test_litellm/proxy/conftest.py index aeca28777d..20236ebdf4 100644 --- a/tests/test_litellm/proxy/conftest.py +++ b/tests/test_litellm/proxy/conftest.py @@ -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. diff --git a/tests/test_litellm/proxy/realtime_endpoints/test_realtime_webrtc_endpoints.py b/tests/test_litellm/proxy/realtime_endpoints/test_realtime_webrtc_endpoints.py index 99b6335ce8..367c89a05f 100644 --- a/tests/test_litellm/proxy/realtime_endpoints/test_realtime_webrtc_endpoints.py +++ b/tests/test_litellm/proxy/realtime_endpoints/test_realtime_webrtc_endpoints.py @@ -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(