mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-06 02:48:13 +00:00
Merge pull request #42118 from BerriAI/litellm_migrate_tests_p3
test(unit): migrate wave 1 phase 3 anthropic, apiserpent, azure and azure_ai legacy tests
This commit is contained in:
commit
743f3a1388
43 changed files with 53 additions and 139 deletions
|
|
@ -1,4 +1,5 @@
|
|||
import os
|
||||
from collections.abc import Iterator
|
||||
from typing import Final
|
||||
|
||||
import pytest
|
||||
|
|
@ -6,7 +7,19 @@ from pytest_socket import enable_socket, socket_allow_hosts
|
|||
|
||||
os.environ["LITELLM_LOCAL_MODEL_COST_MAP"] = "True"
|
||||
|
||||
import litellm # noqa: E402 # litellm reads LITELLM_LOCAL_MODEL_COST_MAP at import
|
||||
import litellm.router as litellm_router_module # noqa: E402 # same import-time dependency
|
||||
import litellm.utils as litellm_utils_module # noqa: E402 # same import-time dependency
|
||||
|
||||
LOOPBACK_HOSTS: Final = ["127.0.0.1", "::1"]
|
||||
AMBIENT_AZURE_CREDENTIAL_ENV_VARS: Final = (
|
||||
"AZURE_AD_TOKEN",
|
||||
"AZURE_TENANT_ID",
|
||||
"AZURE_CLIENT_ID",
|
||||
"AZURE_CLIENT_SECRET",
|
||||
"AZURE_USERNAME",
|
||||
"AZURE_PASSWORD",
|
||||
)
|
||||
|
||||
|
||||
def _allow_loopback_only() -> None:
|
||||
|
|
@ -21,5 +34,38 @@ def pytest_runtest_setup() -> None:
|
|||
_allow_loopback_only()
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def isolate_router_model_cost_state() -> Iterator[None]:
|
||||
original_live_routers: Final = frozenset(litellm_router_module._live_routers)
|
||||
original_runtime_registered_model_cost: Final = {
|
||||
model_key: dict(model_value)
|
||||
for model_key, model_value in litellm_utils_module._runtime_registered_model_cost.items()
|
||||
}
|
||||
yield
|
||||
for router in tuple(litellm_router_module._live_routers):
|
||||
litellm_router_module._live_routers.discard(router)
|
||||
for router in original_live_routers:
|
||||
litellm_router_module._live_routers.add(router)
|
||||
litellm_utils_module._runtime_registered_model_cost.clear()
|
||||
litellm_utils_module._runtime_registered_model_cost.update(original_runtime_registered_model_cost)
|
||||
litellm_utils_module._invalidate_model_cost_lowercase_map()
|
||||
litellm.get_model_info.cache_clear()
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def local_model_cost_map(monkeypatch: pytest.MonkeyPatch) -> Iterator[None]:
|
||||
monkeypatch.setenv("LITELLM_LOCAL_MODEL_COST_MAP", "True")
|
||||
monkeypatch.setattr(litellm, "model_cost", litellm.get_model_cost_map(url=""))
|
||||
litellm.get_model_info.cache_clear()
|
||||
yield
|
||||
litellm.get_model_info.cache_clear()
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def no_ambient_azure_credentials(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
for name in AMBIENT_AZURE_CREDENTIAL_ENV_VARS:
|
||||
monkeypatch.delenv(name, raising=False)
|
||||
|
||||
|
||||
def pytest_sessionfinish() -> None:
|
||||
enable_socket()
|
||||
|
|
|
|||
0
tests/unit/llms/anthropic/files/__init__.py
Normal file
0
tests/unit/llms/anthropic/files/__init__.py
Normal file
0
tests/unit/llms/anthropic/messages/__init__.py
Normal file
0
tests/unit/llms/anthropic/messages/__init__.py
Normal file
0
tests/unit/llms/apiserpent/__init__.py
Normal file
0
tests/unit/llms/apiserpent/__init__.py
Normal file
0
tests/unit/llms/azure/__init__.py
Normal file
0
tests/unit/llms/azure/__init__.py
Normal file
0
tests/unit/llms/azure/image_edit/__init__.py
Normal file
0
tests/unit/llms/azure/image_edit/__init__.py
Normal file
0
tests/unit/llms/azure/image_generation/__init__.py
Normal file
0
tests/unit/llms/azure/image_generation/__init__.py
Normal file
|
|
@ -133,88 +133,6 @@ def test_azure_image_generation_flattens_extra_body():
|
|||
assert data["size"] == "1024x1024"
|
||||
|
||||
|
||||
def test_azure_image_generation_creates_token_provider_from_credentials():
|
||||
"""
|
||||
Test that azure_ad_token_provider is created from tenant_id, client_id, client_secret.
|
||||
|
||||
This test verifies the fix in images/main.py where we now create the
|
||||
azure_ad_token_provider from credentials in litellm_params if it's not already provided.
|
||||
"""
|
||||
# Simulate the fix in images/main.py
|
||||
litellm_params_dict = {
|
||||
"tenant_id": "test-tenant-id",
|
||||
"client_id": "test-client-id",
|
||||
"client_secret": "test-client-secret",
|
||||
"azure_scope": None,
|
||||
}
|
||||
|
||||
azure_ad_token_provider = None
|
||||
|
||||
# This is the logic we added in images/main.py
|
||||
if azure_ad_token_provider is None:
|
||||
tenant_id = litellm_params_dict.get("tenant_id")
|
||||
client_id = litellm_params_dict.get("client_id")
|
||||
client_secret = litellm_params_dict.get("client_secret")
|
||||
azure_scope = (
|
||||
litellm_params_dict.get("azure_scope")
|
||||
or "https://cognitiveservices.azure.com/.default"
|
||||
)
|
||||
|
||||
# Verify the credentials are extracted correctly
|
||||
assert tenant_id == "test-tenant-id"
|
||||
assert client_id == "test-client-id"
|
||||
assert client_secret == "test-client-secret"
|
||||
assert azure_scope == "https://cognitiveservices.azure.com/.default"
|
||||
|
||||
# Verify the condition to create token provider is met
|
||||
assert (
|
||||
tenant_id and client_id and client_secret
|
||||
), "Credentials should be present to create token provider"
|
||||
|
||||
|
||||
def test_azure_image_generation_headers_without_api_key():
|
||||
"""
|
||||
Test that when api_key is None, the api-key header is not added to headers.
|
||||
|
||||
This prevents the httpx TypeError: "Header value must be str or bytes, not <class 'NoneType'>"
|
||||
that was occurring when api_key was None and being set in headers.
|
||||
|
||||
This is a unit test for the fix in images/main.py where we now check:
|
||||
if api_key is not None:
|
||||
default_headers["api-key"] = api_key
|
||||
"""
|
||||
from litellm.images.main import image_generation
|
||||
|
||||
# Test the header building logic directly
|
||||
api_key = None
|
||||
|
||||
default_headers = {
|
||||
"Content-Type": "application/json",
|
||||
}
|
||||
|
||||
# This is the fix: only add api-key if it's not None
|
||||
if api_key is not None:
|
||||
default_headers["api-key"] = api_key
|
||||
|
||||
# Verify api-key is not in headers when api_key is None
|
||||
assert "api-key" not in default_headers
|
||||
|
||||
# Verify Content-Type is still there
|
||||
assert default_headers["Content-Type"] == "application/json"
|
||||
|
||||
# Test with a valid api_key
|
||||
api_key = "valid-key-123"
|
||||
default_headers_with_key = {
|
||||
"Content-Type": "application/json",
|
||||
}
|
||||
if api_key is not None:
|
||||
default_headers_with_key["api-key"] = api_key
|
||||
|
||||
# Verify api-key is added when api_key is valid
|
||||
assert "api-key" in default_headers_with_key
|
||||
assert default_headers_with_key["api-key"] == "valid-key-123"
|
||||
|
||||
|
||||
def test_azure_image_generation_drop_params_response_format():
|
||||
"""
|
||||
Test that unsupported params like response_format are dropped when drop_params=True.
|
||||
0
tests/unit/llms/azure/passthrough/__init__.py
Normal file
0
tests/unit/llms/azure/passthrough/__init__.py
Normal file
0
tests/unit/llms/azure/realtime/__init__.py
Normal file
0
tests/unit/llms/azure/realtime/__init__.py
Normal file
|
|
@ -426,41 +426,6 @@ async def test_async_realtime_beta_without_api_version_raises():
|
|||
)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_realtime_protocol_env_var_fallback():
|
||||
"""
|
||||
Test that LITELLM_AZURE_REALTIME_PROTOCOL env var is used as fallback.
|
||||
Fixes #22127: no way to set realtime_protocol from config.
|
||||
"""
|
||||
from litellm.realtime_api.main import _arealtime
|
||||
from litellm.types.router import GenericLiteLLMParams
|
||||
|
||||
with patch.dict(os.environ, {"LITELLM_AZURE_REALTIME_PROTOCOL": "v1"}):
|
||||
# Create a GenericLiteLLMParams without realtime_protocol
|
||||
litellm_params = GenericLiteLLMParams()
|
||||
# The env var should be picked up as fallback
|
||||
realtime_protocol = (
|
||||
{}.get("realtime_protocol")
|
||||
or litellm_params.get("realtime_protocol")
|
||||
or os.environ.get("LITELLM_AZURE_REALTIME_PROTOCOL")
|
||||
or "beta"
|
||||
)
|
||||
assert realtime_protocol == "v1"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_realtime_protocol_from_litellm_params():
|
||||
"""
|
||||
Test that realtime_protocol is read from litellm_params (config.yaml extra field).
|
||||
Fixes #22127: realtime_protocol in litellm_params was not used.
|
||||
"""
|
||||
from litellm.types.router import GenericLiteLLMParams
|
||||
|
||||
# Simulate config.yaml with realtime_protocol as an extra field
|
||||
litellm_params = GenericLiteLLMParams(realtime_protocol="GA")
|
||||
assert litellm_params.get("realtime_protocol") == "GA"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_arealtime_transcription_intent_defaults_to_ga(monkeypatch):
|
||||
"""
|
||||
|
|
@ -742,7 +707,7 @@ async def test_realtime_health_check_uses_bearer_token_when_no_api_key(monkeypat
|
|||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_arealtime_forwards_deployment_azure_ad_token(monkeypatch):
|
||||
async def test_arealtime_forwards_deployment_azure_ad_token(monkeypatch, no_ambient_azure_credentials):
|
||||
"""
|
||||
The router binds a deployment's `azure_ad_token` to `_arealtime`'s named parameter rather than
|
||||
**kwargs, so it must still reach the handler.
|
||||
0
tests/unit/llms/azure/response/__init__.py
Normal file
0
tests/unit/llms/azure/response/__init__.py
Normal file
0
tests/unit/llms/azure/search/__init__.py
Normal file
0
tests/unit/llms/azure/search/__init__.py
Normal file
0
tests/unit/llms/azure/text_to_speech/__init__.py
Normal file
0
tests/unit/llms/azure/text_to_speech/__init__.py
Normal file
0
tests/unit/llms/azure/vector_stores/__init__.py
Normal file
0
tests/unit/llms/azure/vector_stores/__init__.py
Normal file
0
tests/unit/llms/azure_ai/__init__.py
Normal file
0
tests/unit/llms/azure_ai/__init__.py
Normal file
0
tests/unit/llms/azure_ai/chat/__init__.py
Normal file
0
tests/unit/llms/azure_ai/chat/__init__.py
Normal file
|
|
@ -352,21 +352,6 @@ def test_azure_model_router_stamps_selected_model_on_hidden_params():
|
|||
)
|
||||
|
||||
|
||||
def test_azure_model_router_stamp_does_not_leak_across_responses():
|
||||
"""
|
||||
ModelResponse declares _hidden_params as a class-level dict, so the stamp has to be written
|
||||
as a fresh dict. Mutating in place would bleed the selected model into unrelated responses.
|
||||
"""
|
||||
from litellm.llms.azure_ai.common_utils import (
|
||||
AZURE_MODEL_ROUTER_SELECTED_MODEL_KEY,
|
||||
)
|
||||
from litellm.types.utils import ModelResponse
|
||||
|
||||
untouched = ModelResponse()
|
||||
|
||||
assert AZURE_MODEL_ROUTER_SELECTED_MODEL_KEY not in (untouched._hidden_params or {})
|
||||
|
||||
|
||||
def test_drop_tool_level_extra_fields_strips_copilot_mcp_server_name():
|
||||
"""
|
||||
Regression test: Azure AI returns 400 when tools contain copilot_mcp_server_name.
|
||||
0
tests/unit/llms/azure_ai/embed/__init__.py
Normal file
0
tests/unit/llms/azure_ai/embed/__init__.py
Normal file
0
tests/unit/llms/azure_ai/image_edit/__init__.py
Normal file
0
tests/unit/llms/azure_ai/image_edit/__init__.py
Normal file
|
|
@ -41,7 +41,7 @@ def test_azure_ai_url_generation():
|
|||
assert complete_url == expected_url
|
||||
|
||||
|
||||
def test_azure_ai_validate_environment_with_entra_token(monkeypatch):
|
||||
def test_azure_ai_validate_environment_with_entra_token(monkeypatch, no_ambient_azure_credentials):
|
||||
monkeypatch.delenv("AZURE_AI_API_KEY", raising=False)
|
||||
monkeypatch.setattr(litellm, "api_key", None)
|
||||
config = AzureFoundryFluxImageEditConfig()
|
||||
|
|
@ -55,7 +55,7 @@ def test_azure_ai_validate_environment_with_entra_token(monkeypatch):
|
|||
assert headers == {"Authorization": "Bearer entra-token"}
|
||||
|
||||
|
||||
def test_flux2_validate_environment_with_entra_token(monkeypatch):
|
||||
def test_flux2_validate_environment_with_entra_token(monkeypatch, no_ambient_azure_credentials):
|
||||
monkeypatch.delenv("AZURE_AI_API_KEY", raising=False)
|
||||
monkeypatch.setattr(litellm, "api_key", None)
|
||||
config = AzureFoundryFlux2ImageEditConfig()
|
||||
|
|
@ -174,7 +174,7 @@ class TestAzureMAIImageEdit:
|
|||
assert image_response.usage.total_tokens == 1024
|
||||
|
||||
|
||||
def test_mai_validate_environment_with_entra_token(monkeypatch):
|
||||
def test_mai_validate_environment_with_entra_token(monkeypatch, no_ambient_azure_credentials):
|
||||
monkeypatch.delenv("AZURE_AI_API_KEY", raising=False)
|
||||
monkeypatch.setattr(litellm, "api_key", None)
|
||||
|
||||
0
tests/unit/llms/azure_ai/ocr/__init__.py
Normal file
0
tests/unit/llms/azure_ai/ocr/__init__.py
Normal file
0
tests/unit/llms/azure_ai/passthrough/__init__.py
Normal file
0
tests/unit/llms/azure_ai/passthrough/__init__.py
Normal file
|
|
@ -256,13 +256,13 @@ def test_serverless_host_gets_a_bearer_token():
|
|||
assert "api-key" not in headers
|
||||
|
||||
|
||||
def test_entra_token_is_used_when_the_deployment_has_no_api_key():
|
||||
def test_entra_token_is_used_when_the_deployment_has_no_api_key(no_ambient_azure_credentials):
|
||||
headers = _auth_headers(api_key=None, api_base=FOUNDRY_BASE, litellm_params={"azure_ad_token": "entra-token"})
|
||||
|
||||
assert headers["Authorization"] == "Bearer entra-token"
|
||||
|
||||
|
||||
def test_no_credentials_at_all_raises():
|
||||
def test_no_credentials_at_all_raises(no_ambient_azure_credentials):
|
||||
with pytest.raises(ValueError, match="Missing Azure AI credentials"):
|
||||
_auth_headers(api_key=None, api_base=FOUNDRY_BASE)
|
||||
|
||||
0
tests/unit/llms/azure_ai/rerank/__init__.py
Normal file
0
tests/unit/llms/azure_ai/rerank/__init__.py
Normal file
|
|
@ -105,7 +105,7 @@ class TestAzureAIRerankConfigValidateEnvironment:
|
|||
|
||||
assert headers["Authorization"] == "Bearer my-key"
|
||||
|
||||
def test_falls_back_to_entra_token(self, monkeypatch):
|
||||
def test_falls_back_to_entra_token(self, monkeypatch, no_ambient_azure_credentials):
|
||||
monkeypatch.delenv("AZURE_AI_API_KEY", raising=False)
|
||||
monkeypatch.setattr(litellm, "azure_key", None)
|
||||
|
||||
0
tests/unit/llms/azure_ai/responses/__init__.py
Normal file
0
tests/unit/llms/azure_ai/responses/__init__.py
Normal file
Loading…
Add table
Reference in a new issue