mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-02 02:11:58 +00:00
test(proxy): move auth, hooks, policy_engine and client tests into tests/unit/proxy (#43998)
* test(proxy): move auth, hooks, policy_engine and client tests into tests/unit/proxy Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * test(proxy): stub HIBP through respx by disabling the aiohttp transport Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * test(proxy): share the httpx transport fixture across proxy unit tests Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * test(proxy): restore proxy globals without a missing-value sentinel Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * test(proxy): package moved dirs and stub the login breach check at the HTTP boundary Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * test(proxy): isolate the mcp server manager per test Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> --------- Co-authored-by: yuneng <yuneng@berri.ai> Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
parent
d9f73245be
commit
39e31958f8
126 changed files with 152 additions and 12 deletions
4
.github/merge-smoke-tests.json
vendored
4
.github/merge-smoke-tests.json
vendored
|
|
@ -3,8 +3,8 @@
|
|||
"CHAT-JSON": "tests/unit/llms/openai/test_openai.py::test_acompletion_returns_json_reply_over_injected_transport",
|
||||
"CHAT-TEXT-STREAM": "tests/unit/llms/openai/test_openai.py::test_acompletion_streams_text_deltas_over_injected_transport",
|
||||
"CHAT-TOOL-STREAM": "tests/unit/llms/openai/test_openai.py::test_acompletion_streams_tool_call_arguments_over_injected_transport",
|
||||
"MODEL-ALLOW": "tests/test_litellm/proxy/auth/test_auth_checks.py::test_can_object_call_model_allows_listed_model_for_key",
|
||||
"MODEL-DENY": "tests/test_litellm/proxy/auth/test_auth_checks.py::test_can_object_call_model_denials_return_forbidden[key-key_model_access_denied]",
|
||||
"MODEL-ALLOW": "tests/unit/proxy/auth/test_auth_checks_object_access_and_lookup.py::test_can_object_call_model_allows_listed_model_for_key",
|
||||
"MODEL-DENY": "tests/unit/proxy/auth/test_auth_checks_object_access_and_lookup.py::test_can_object_call_model_denials_return_forbidden[key-key_model_access_denied]",
|
||||
"COST-EXPLICIT": "tests/unit/test_cost_calculator.py::test_completion_cost_charges_explicit_per_token_rates_over_registered_ones",
|
||||
"COST-ZERO": "tests/unit/test_cost_calculator.py::test_completion_cost_is_zero_when_explicit_rates_are_zero",
|
||||
"LOG-CONTENT-ON": "tests/unit/litellm_core_utils/test_litellm_logging.py::test_standard_logging_payload_keeps_message_content_when_message_logging_is_on",
|
||||
|
|
|
|||
19
.github/workflows/test-unit.yml
vendored
19
.github/workflows/test-unit.yml
vendored
|
|
@ -119,10 +119,19 @@ jobs:
|
|||
- shard: proxy-auth
|
||||
artifact-name: proxy-auth
|
||||
test-path: >-
|
||||
tests/test_litellm/proxy/auth
|
||||
tests/test_litellm/proxy/hooks
|
||||
tests/test_litellm/proxy/policy_engine
|
||||
tests/test_litellm/proxy/client
|
||||
tests/unit/proxy/auth
|
||||
tests/unit/proxy/hooks
|
||||
tests/unit/proxy/policy_engine
|
||||
tests/unit/proxy/client
|
||||
--ignore=tests/unit/proxy/auth/test_auth_checks.py
|
||||
--ignore=tests/unit/proxy/auth/test_user_api_key_auth.py
|
||||
--ignore=tests/unit/proxy/auth/test_default_end_user_budget_simple.py
|
||||
--ignore=tests/unit/proxy/auth/test_jwt.py
|
||||
--ignore=tests/unit/proxy/auth/test_models_fallback_endpoint.py
|
||||
--ignore=tests/unit/proxy/auth/test_multipart_bypass_repro.py
|
||||
--ignore=tests/unit/proxy/auth/test_proxy_routes.py
|
||||
--ignore=tests/unit/proxy/hooks/test_banned_keyword_list.py
|
||||
--ignore=tests/unit/proxy/hooks/test_unit_test_max_model_budget_limiter.py
|
||||
workers: 2
|
||||
reruns: 2
|
||||
timeout-minutes: 20
|
||||
|
|
@ -190,6 +199,8 @@ jobs:
|
|||
tests/test_litellm/proxy/types_utils
|
||||
tests/test_litellm/proxy/logging_endpoints
|
||||
tests/test_litellm/proxy/test_*.py
|
||||
tests/unit/proxy/test_proxy_server_endpoints_and_startup.py
|
||||
tests/unit/proxy/test_proxy_utils_model_creation_and_error_logging.py
|
||||
unit-flag: proxy-infra
|
||||
workers: 4
|
||||
reruns: 2
|
||||
|
|
|
|||
4
Makefile
4
Makefile
|
|
@ -324,10 +324,10 @@ test-unit-proxy-guardrails: install-test-deps
|
|||
$(UV_RUN) pytest tests/test_litellm/proxy/guardrails tests/test_litellm/proxy/management_endpoints tests/test_litellm/proxy/management_helpers --tb=short -vv -n 4 --durations=20
|
||||
|
||||
test-unit-proxy-core: install-test-deps
|
||||
$(UV_RUN) pytest tests/test_litellm/proxy/auth tests/test_litellm/proxy/client tests/test_litellm/proxy/db tests/test_litellm/proxy/hooks tests/test_litellm/proxy/policy_engine --tb=short -vv -n 4 --durations=20
|
||||
$(UV_RUN) pytest tests/unit/proxy/auth tests/unit/proxy/client tests/test_litellm/proxy/db tests/unit/proxy/hooks tests/unit/proxy/policy_engine --tb=short -vv -n 4 --durations=20
|
||||
|
||||
test-unit-proxy-misc: install-test-deps
|
||||
$(UV_RUN) pytest tests/test_litellm/proxy/_experimental tests/test_litellm/proxy/agent_endpoints tests/test_litellm/proxy/anthropic_endpoints tests/test_litellm/proxy/common_utils tests/test_litellm/proxy/discovery_endpoints tests/test_litellm/proxy/experimental tests/test_litellm/proxy/google_endpoints tests/test_litellm/proxy/health_endpoints tests/test_litellm/proxy/image_endpoints tests/test_litellm/proxy/middleware tests/test_litellm/proxy/openai_files_endpoint tests/test_litellm/proxy/pass_through_endpoints tests/test_litellm/proxy/prompts tests/test_litellm/proxy/public_endpoints tests/test_litellm/proxy/response_api_endpoints tests/test_litellm/proxy/shutdown tests/test_litellm/proxy/spend_tracking tests/test_litellm/proxy/ui_crud_endpoints tests/test_litellm/proxy/vector_store_endpoints tests/test_litellm/proxy/test_*.py --tb=short -vv -n 4 --durations=20
|
||||
$(UV_RUN) pytest tests/test_litellm/proxy/_experimental tests/test_litellm/proxy/agent_endpoints tests/test_litellm/proxy/anthropic_endpoints tests/test_litellm/proxy/common_utils tests/test_litellm/proxy/discovery_endpoints tests/test_litellm/proxy/experimental tests/test_litellm/proxy/google_endpoints tests/test_litellm/proxy/health_endpoints tests/test_litellm/proxy/image_endpoints tests/test_litellm/proxy/middleware tests/test_litellm/proxy/openai_files_endpoint tests/test_litellm/proxy/pass_through_endpoints tests/test_litellm/proxy/prompts tests/test_litellm/proxy/public_endpoints tests/test_litellm/proxy/response_api_endpoints tests/test_litellm/proxy/shutdown tests/test_litellm/proxy/spend_tracking tests/test_litellm/proxy/ui_crud_endpoints tests/test_litellm/proxy/vector_store_endpoints tests/test_litellm/proxy/test_*.py tests/unit/proxy/test_proxy_server_endpoints_and_startup.py tests/unit/proxy/test_proxy_utils_model_creation_and_error_logging.py tests/unit/proxy/_experimental/mcp_server/test_mcp_server_tool_calls_and_headers.py --tb=short -vv -n 4 --durations=20
|
||||
|
||||
test-unit-integrations: install-test-deps
|
||||
$(UV_RUN) pytest tests/unit/integrations --tb=short -vv -n 4 --durations=20
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
"""Runs the auth prefetch's raw SQL against a real Postgres: the join must bind the membership to the requested
|
||||
team and hand the getters rows they validate. The per-regime round-trip counts are unit-tested with fakes in
|
||||
tests/test_litellm/proxy/auth/test_auth_object_prefetch.py."""
|
||||
tests/unit/proxy/auth/test_auth_object_prefetch.py."""
|
||||
|
||||
import json
|
||||
from unittest.mock import AsyncMock, MagicMock
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import asyncio
|
||||
import importlib
|
||||
import os
|
||||
|
||||
import pytest
|
||||
|
||||
|
|
@ -76,3 +77,62 @@ def config_only_mcp_manager_factory():
|
|||
return None
|
||||
|
||||
return ConfigOnlyManager
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def _hermetic_mcp_server_registry():
|
||||
from litellm.proxy._experimental.mcp_server.mcp_server_manager import (
|
||||
global_mcp_server_manager,
|
||||
)
|
||||
|
||||
saved_registry = dict(global_mcp_server_manager.registry)
|
||||
saved_config_servers = dict(global_mcp_server_manager.config_mcp_servers)
|
||||
saved_tool_mapping = dict(global_mcp_server_manager.tool_name_to_mcp_server_name_mapping)
|
||||
saved_oauth_slots = global_mcp_server_manager._oauth_discovery_slots
|
||||
global_mcp_server_manager.registry.clear()
|
||||
global_mcp_server_manager.config_mcp_servers.clear()
|
||||
global_mcp_server_manager.tool_name_to_mcp_server_name_mapping.clear()
|
||||
global_mcp_server_manager._oauth_discovery_slots = ()
|
||||
try:
|
||||
yield
|
||||
finally:
|
||||
global_mcp_server_manager.registry.clear()
|
||||
global_mcp_server_manager.registry.update(saved_registry)
|
||||
global_mcp_server_manager.config_mcp_servers.clear()
|
||||
global_mcp_server_manager.config_mcp_servers.update(saved_config_servers)
|
||||
global_mcp_server_manager.tool_name_to_mcp_server_name_mapping.clear()
|
||||
global_mcp_server_manager.tool_name_to_mcp_server_name_mapping.update(saved_tool_mapping)
|
||||
global_mcp_server_manager._oauth_discovery_slots = saved_oauth_slots
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def _hermetic_server_root_path():
|
||||
saved = os.environ.pop("SERVER_ROOT_PATH", None)
|
||||
try:
|
||||
yield
|
||||
finally:
|
||||
if saved is not None:
|
||||
os.environ["SERVER_ROOT_PATH"] = saved
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def _mcp_request_ctx():
|
||||
def _mcp_request_ctx(**overrides):
|
||||
from types import SimpleNamespace
|
||||
|
||||
from mcp.server.context import ServerRequestContext
|
||||
|
||||
kwargs = {
|
||||
"session": SimpleNamespace(),
|
||||
"lifespan_context": {},
|
||||
"protocol_version": "2025-06-18",
|
||||
"method": "",
|
||||
"params": None,
|
||||
"request_id": 1,
|
||||
"meta": None,
|
||||
"request": None,
|
||||
}
|
||||
kwargs.update(overrides)
|
||||
return ServerRequestContext(**kwargs)
|
||||
|
||||
return _mcp_request_ctx
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ from unittest.mock import AsyncMock, MagicMock, patch
|
|||
|
||||
import httpx
|
||||
import pytest
|
||||
import respx
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from litellm.proxy.auth.login_throttle import LoginThrottle
|
||||
|
|
@ -1978,10 +1979,15 @@ class TestDisableEnvCredentialLogin:
|
|||
assert exc_info.value.code == "401"
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_db_user_login_still_works_when_disabled(self):
|
||||
@respx.mock
|
||||
async def test_db_user_login_still_works_when_disabled(self, httpx_transport):
|
||||
master_key = "sk-1234"
|
||||
user_email = "admin@example.com"
|
||||
password = "Str0ng!Passw0rd"
|
||||
sha1 = hashlib.sha1(password.encode("utf-8"), usedforsecurity=False).hexdigest().upper()
|
||||
respx.get(f"https://api.pwnedpasswords.com/range/{sha1[:5]}").mock(
|
||||
return_value=httpx.Response(200, text="AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA:41")
|
||||
)
|
||||
|
||||
mock_user = LiteLLM_UserTable(
|
||||
user_id="db-admin-1",
|
||||
|
|
@ -632,7 +632,7 @@ async def test_claim_token_rejects_short_password_before_consuming_invite():
|
|||
|
||||
@pytest.mark.asyncio
|
||||
@respx.mock
|
||||
async def test_claim_token_rejects_breached_password_before_consuming_invite():
|
||||
async def test_claim_token_rejects_breached_password_before_consuming_invite(httpx_transport):
|
||||
"""A password found in the HIBP corpus must be rejected and never stored."""
|
||||
from litellm.proxy.proxy_server import claim_onboarding_link
|
||||
|
||||
|
|
@ -666,7 +666,7 @@ async def test_claim_token_rejects_breached_password_before_consuming_invite():
|
|||
|
||||
@pytest.mark.asyncio
|
||||
@respx.mock
|
||||
async def test_claim_token_fails_open_when_hibp_unreachable():
|
||||
async def test_claim_token_fails_open_when_hibp_unreachable(httpx_transport):
|
||||
"""An HIBP outage must never block onboarding: the claim proceeds."""
|
||||
from litellm.proxy.proxy_server import claim_onboarding_link
|
||||
|
||||
|
|
@ -4,12 +4,15 @@ import asyncio
|
|||
import copy
|
||||
import inspect
|
||||
import warnings
|
||||
from collections.abc import Iterator
|
||||
from typing import Dict
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
import litellm
|
||||
import litellm.proxy.proxy_server
|
||||
from tests.unit.litellm_core_utils.fake_secret_vault import FakeSecretVault
|
||||
|
||||
|
||||
# Top-level assignments of these types are the ones importlib.reload(litellm)
|
||||
|
|
@ -148,3 +151,63 @@ def pytest_collection_modifyitems(config, items):
|
|||
|
||||
# Reorder the items list
|
||||
items[:] = custom_logger_tests + other_tests
|
||||
|
||||
|
||||
_PROXY_MODULE_GLOBALS_TO_ISOLATE = (
|
||||
"master_key",
|
||||
"prisma_client",
|
||||
"llm_router",
|
||||
)
|
||||
|
||||
_proxy_module_globals_snapshot = pytest.StashKey[Dict[str, object]]()
|
||||
|
||||
|
||||
@pytest.hookimpl(hookwrapper=True)
|
||||
def pytest_runtest_setup(item):
|
||||
from litellm.proxy import proxy_server
|
||||
|
||||
item.stash[_proxy_module_globals_snapshot] = {
|
||||
name: vars(proxy_server)[name]
|
||||
for name in _PROXY_MODULE_GLOBALS_TO_ISOLATE
|
||||
if name in vars(proxy_server)
|
||||
}
|
||||
yield
|
||||
|
||||
|
||||
@pytest.hookimpl(hookwrapper=True)
|
||||
def pytest_runtest_teardown(item, nextitem):
|
||||
yield
|
||||
snapshot = item.stash.get(_proxy_module_globals_snapshot, None)
|
||||
if snapshot is None:
|
||||
return
|
||||
from litellm.proxy import proxy_server
|
||||
|
||||
for name in _PROXY_MODULE_GLOBALS_TO_ISOLATE:
|
||||
if name in snapshot:
|
||||
setattr(proxy_server, name, snapshot[name])
|
||||
elif name in vars(proxy_server):
|
||||
delattr(proxy_server, name)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def secret_vault_factory() -> type[FakeSecretVault]:
|
||||
return FakeSecretVault
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def httpx_transport(monkeypatch: pytest.MonkeyPatch) -> Iterator[None]:
|
||||
monkeypatch.setattr(litellm, "disable_aiohttp_transport", True)
|
||||
litellm.in_memory_llm_clients_cache.flush_cache()
|
||||
yield
|
||||
litellm.in_memory_llm_clients_cache.flush_cache()
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def _reset_graceful_shutdown_state():
|
||||
from litellm.proxy.shutdown.graceful_shutdown_manager import (
|
||||
GracefulShutdownManager,
|
||||
)
|
||||
|
||||
GracefulShutdownManager.reset()
|
||||
yield
|
||||
GracefulShutdownManager.reset()
|
||||
|
|
|
|||
0
tests/unit/proxy/hooks/litellm_skills/__init__.py
Normal file
0
tests/unit/proxy/hooks/litellm_skills/__init__.py
Normal file
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue