Skip to content
Merged
4 changes: 2 additions & 2 deletions tests/otel_tests/test_prometheus.py
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ async def test_proxy_failure_metrics():
# Check if the failure metric is present and correct - use pattern matching for robustness
# Labels are ordered alphabetically by Prometheus: api_key_alias, client_ip, end_user, exception_class,
# exception_status, hashed_api_key, model_id, requested_model, route, team, team_alias, user, user_agent, user_email
expected_metric_pattern = 'litellm_proxy_failed_requests_metric_total{api_key_alias="None",client_ip="None",end_user="None",exception_class="Openai.RateLimitError",exception_status="429",hashed_api_key="88dc28d0f030c55ed4ab77ed8faf098196cb1c05df778539800c9f1243fe6b4b",model_id="None",requested_model="fake-azure-endpoint",route="/chat/completions",team="None",team_alias="None",user="*******_user_id",user_agent="None",user_email="None"}'
expected_metric_pattern = 'litellm_proxy_failed_requests_metric_total{api_key_alias="None",client_ip="None",end_user="None",exception_class="Openai.RateLimitError",exception_status="429",hashed_api_key="88dc28d0f030c55ed4ab77ed8faf098196cb1c05df778539800c9f1243fe6b4b",model_id="None",requested_model="fake-azure-endpoint",route="/chat/completions",team="None",team_alias="None",user="default_user_id",user_agent="None",user_email="None"}'

# Check if the pattern is in metrics
assert any(
Expand All @@ -118,7 +118,7 @@ async def test_proxy_failure_metrics():
# Check total requests metric
# Labels are ordered alphabetically: api_key_alias, client_ip, end_user, hashed_api_key, model_id,
# requested_model, route, status_code, team, team_alias, user, user_agent, user_email
total_requests_pattern = 'litellm_proxy_total_requests_metric_total{api_key_alias="None",client_ip="None",end_user="None",hashed_api_key="88dc28d0f030c55ed4ab77ed8faf098196cb1c05df778539800c9f1243fe6b4b",model_id="None",requested_model="fake-azure-endpoint",route="/chat/completions",status_code="429",team="None",team_alias="None",user="*******_user_id",user_agent="None",user_email="None"}'
total_requests_pattern = 'litellm_proxy_total_requests_metric_total{api_key_alias="None",client_ip="None",end_user="None",hashed_api_key="88dc28d0f030c55ed4ab77ed8faf098196cb1c05df778539800c9f1243fe6b4b",model_id="None",requested_model="fake-azure-endpoint",route="/chat/completions",status_code="429",team="None",team_alias="None",user="default_user_id",user_agent="None",user_email="None"}'

assert any(
total_requests_pattern in line for line in metrics.split("\n")
Expand Down
72 changes: 25 additions & 47 deletions tests/test_litellm/containers/test_container_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
list_containers,
retrieve_container,
)
from litellm.main import base_llm_http_handler
from litellm.litellm_core_utils.litellm_logging import Logging as LiteLLMLogging
from litellm.llms.openai.containers.transformation import OpenAIContainerConfig
from litellm.router import Router
Expand Down Expand Up @@ -63,9 +64,7 @@ def test_create_container_basic(self):
name="Test Container"
)

with patch('litellm.containers.main.base_llm_http_handler') as mock_handler:
mock_handler.container_create_handler.return_value = mock_response

with patch.object(base_llm_http_handler, 'container_create_handler', return_value=mock_response):
response = create_container(
name="Test Container",
custom_llm_provider="openai"
Expand All @@ -89,9 +88,7 @@ def test_create_container_with_expires_after(self):
name="Expiring Container"
)

with patch('litellm.containers.main.base_llm_http_handler') as mock_handler:
mock_handler.container_create_handler.return_value = mock_response

with patch.object(base_llm_http_handler, 'container_create_handler', return_value=mock_response):
response = create_container(
name="Expiring Container",
expires_after={"anchor": "last_active_at", "minutes": 30},
Expand All @@ -113,9 +110,7 @@ def test_create_container_with_file_ids(self):
name="Container with Files"
)

with patch('litellm.containers.main.base_llm_http_handler') as mock_handler:
mock_handler.container_create_handler.return_value = mock_response

with patch.object(base_llm_http_handler, 'container_create_handler', return_value=mock_response):
response = create_container(
name="Container with Files",
file_ids=["file_123", "file_456"],
Expand All @@ -137,9 +132,7 @@ async def test_acreate_container_basic(self):
name="Async Test Container"
)

with patch('litellm.containers.main.base_llm_http_handler') as mock_handler:
mock_handler.container_create_handler.return_value = mock_response

with patch.object(base_llm_http_handler, 'container_create_handler', return_value=mock_response):
response = await acreate_container(
name="Async Test Container",
custom_llm_provider="openai"
Expand Down Expand Up @@ -171,9 +164,7 @@ async def test_alist_containers_basic(self):
has_more=False
)

with patch('litellm.containers.main.base_llm_http_handler') as mock_handler:
mock_handler.container_list_handler.return_value = mock_response

with patch.object(base_llm_http_handler, 'container_list_handler', return_value=mock_response):
response = await alist_containers(
custom_llm_provider="openai"
)
Expand Down Expand Up @@ -208,18 +199,16 @@ def test_retrieve_container_basic(self, container_id, container_name, status, pr
name=container_name
)

with patch('litellm.containers.main.base_llm_http_handler') as mock_handler:
mock_handler.container_retrieve_handler.return_value = mock_response

with patch.object(base_llm_http_handler, 'container_retrieve_handler', return_value=mock_response) as mock_method:
# Act: Call retrieve_container
response = retrieve_container(
container_id=container_id,
custom_llm_provider=provider
)

# Assert: Verify the handler was called correctly
mock_handler.container_retrieve_handler.assert_called_once()
call_kwargs = mock_handler.container_retrieve_handler.call_args.kwargs
mock_method.assert_called_once()
call_kwargs = mock_method.call_args.kwargs
assert call_kwargs["container_id"] == container_id

# Assert: Verify response structure and content
Expand All @@ -245,9 +234,7 @@ async def test_aretrieve_container_basic(self):
name="Async Retrieved Container"
)

with patch('litellm.containers.main.base_llm_http_handler') as mock_handler:
mock_handler.container_retrieve_handler.return_value = mock_response

with patch.object(base_llm_http_handler, 'container_retrieve_handler', return_value=mock_response):
response = await aretrieve_container(
container_id=container_id,
custom_llm_provider="openai"
Expand All @@ -265,9 +252,7 @@ def test_delete_container_basic(self):
deleted=True
)

with patch('litellm.containers.main.base_llm_http_handler') as mock_handler:
mock_handler.container_delete_handler.return_value = mock_response

with patch.object(base_llm_http_handler, 'container_delete_handler', return_value=mock_response):
response = delete_container(
container_id=container_id,
custom_llm_provider="openai"
Expand All @@ -288,9 +273,7 @@ async def test_adelete_container_basic(self):
deleted=True
)

with patch('litellm.containers.main.base_llm_http_handler') as mock_handler:
mock_handler.container_delete_handler.return_value = mock_response

with patch.object(base_llm_http_handler, 'container_delete_handler', return_value=mock_response):
response = await adelete_container(
container_id=container_id,
custom_llm_provider="openai"
Expand All @@ -302,9 +285,7 @@ async def test_adelete_container_basic(self):

def test_create_container_error_handling(self):
"""Test error handling in container creation."""
with patch('litellm.containers.main.base_llm_http_handler') as mock_handler:
mock_handler.container_create_handler.side_effect = Exception("API Error")

with patch.object(base_llm_http_handler, 'container_create_handler', side_effect=Exception("API Error")):
with pytest.raises(Exception):
create_container(
name="Error Test Container",
Expand All @@ -313,21 +294,20 @@ def test_create_container_error_handling(self):

def test_container_provider_config_retrieval(self):
"""Test that provider config is retrieved correctly."""
mock_response = ContainerObject(
id="cntr_config_test",
object="container",
created_at=1747857508,
status="running",
expires_after={"anchor": "last_active_at", "minutes": 20},
last_active_at=1747857508,
name="Config Test"
)

with patch('litellm.containers.main.ProviderConfigManager') as mock_config_manager:
mock_config_manager.get_provider_container_config.return_value = OpenAIContainerConfig()

with patch('litellm.containers.main.base_llm_http_handler') as mock_handler:
mock_response = ContainerObject(
id="cntr_config_test",
object="container",
created_at=1747857508,
status="running",
expires_after={"anchor": "last_active_at", "minutes": 20},
last_active_at=1747857508,
name="Config Test"
)
mock_handler.container_create_handler.return_value = mock_response

with patch.object(base_llm_http_handler, 'container_create_handler', return_value=mock_response):
response = create_container(
name="Config Test",
custom_llm_provider="openai"
Expand Down Expand Up @@ -355,9 +335,7 @@ async def test_router_acreate_container_without_model(self):
name="Test Container"
)

with patch('litellm.containers.main.base_llm_http_handler') as mock_handler:
mock_handler.container_create_handler.return_value = mock_response

with patch.object(base_llm_http_handler, 'container_create_handler', return_value=mock_response):
result = await router.acreate_container(
name="Test Container",
custom_llm_provider="openai"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,7 @@
import sys
import unittest.mock as mock

import httpx
import pytest
import respx
from httpx import Response

sys.path.insert(0, os.path.abspath("../../.."))
Expand Down Expand Up @@ -38,32 +36,8 @@ def mock_env_vars():
yield


@pytest.fixture
def mock_httpx_client():
with mock.patch(
"litellm_enterprise.enterprise_callbacks.send_emails.resend_email.get_async_httpx_client"
) as mock_client:

mock_response = mock.Mock(spec=Response)
mock_response.status_code = 200
mock_response.json.return_value = {"id": "test_email_id"}
mock_response.raise_for_status.return_value = None

mock_async_client = mock.AsyncMock()
mock_async_client.post.return_value = mock_response

mock_client.return_value = mock_async_client
yield mock_async_client


@pytest.mark.asyncio
@respx.mock
async def test_send_email_success(mock_env_vars, mock_httpx_client):
# Block all HTTP requests at network level to prevent real API calls
respx.post("https://api.resend.com/emails").mock(
return_value=httpx.Response(200, json={"id": "test_email_id"})
)

async def test_send_email_success(mock_env_vars):
# Initialize the logger
logger = ResendEmailLogger()

Expand All @@ -73,14 +47,27 @@ async def test_send_email_success(mock_env_vars, mock_httpx_client):
subject = "Test Subject"
html_body = "<p>Test email body</p>"

# Create mock HTTP client and inject it directly into the logger
# This ensures the mock is used regardless of any caching/import issues
mock_response = mock.Mock(spec=Response)
mock_response.status_code = 200
mock_response.json.return_value = {"id": "test_email_id"}
mock_response.raise_for_status.return_value = None

mock_async_client = mock.AsyncMock()
mock_async_client.post.return_value = mock_response

# Directly inject the mock client to bypass any caching
logger.async_httpx_client = mock_async_client

# Send email
await logger.send_email(
from_email=from_email, to_email=to_email, subject=subject, html_body=html_body
)

# Verify the HTTP client was called correctly
mock_httpx_client.post.assert_called_once()
call_args = mock_httpx_client.post.call_args
mock_async_client.post.assert_called_once()
call_args = mock_async_client.post.call_args

# Verify the URL
assert call_args[1]["url"] == "https://api.resend.com/emails"
Expand All @@ -97,13 +84,7 @@ async def test_send_email_success(mock_env_vars, mock_httpx_client):


@pytest.mark.asyncio
@respx.mock
async def test_send_email_missing_api_key(mock_httpx_client):
# Block all HTTP requests at network level to prevent real API calls
respx.post("https://api.resend.com/emails").mock(
return_value=httpx.Response(200, json={"id": "test_email_id"})
)

async def test_send_email_missing_api_key():
# Remove the API key from environment before initializing logger
original_key = os.environ.pop("RESEND_API_KEY", None)

Expand All @@ -117,22 +98,27 @@ async def test_send_email_missing_api_key(mock_httpx_client):
subject = "Test Subject"
html_body = "<p>Test email body</p>"

# Mock the response to avoid making real HTTP requests
# Create mock HTTP client and inject it directly into the logger
# This ensures the mock is used regardless of any caching issues
mock_response = mock.Mock(spec=Response)
mock_response.raise_for_status.return_value = None

mock_response.status_code = 200
mock_response.json.return_value = {"id": "test_email_id"}
mock_httpx_client.post.return_value = mock_response

mock_async_client = mock.AsyncMock()
mock_async_client.post.return_value = mock_response

# Directly inject the mock client to bypass any caching
logger.async_httpx_client = mock_async_client

# Send email
await logger.send_email(
from_email=from_email, to_email=to_email, subject=subject, html_body=html_body
)

# Verify the HTTP client was called with None as the API key
mock_httpx_client.post.assert_called_once()
call_args = mock_httpx_client.post.call_args
mock_async_client.post.assert_called_once()
call_args = mock_async_client.post.call_args
assert call_args[1]["headers"] == {"Authorization": "Bearer None"}
finally:
# Restore the original key if it existed
Expand All @@ -141,13 +127,7 @@ async def test_send_email_missing_api_key(mock_httpx_client):


@pytest.mark.asyncio
@respx.mock
async def test_send_email_multiple_recipients(mock_env_vars, mock_httpx_client):
# Block all HTTP requests at network level to prevent real API calls
respx.post("https://api.resend.com/emails").mock(
return_value=httpx.Response(200, json={"id": "test_email_id"})
)

async def test_send_email_multiple_recipients(mock_env_vars):
# Initialize the logger
logger = ResendEmailLogger()

Expand All @@ -157,21 +137,25 @@ async def test_send_email_multiple_recipients(mock_env_vars, mock_httpx_client):
subject = "Test Subject"
html_body = "<p>Test email body</p>"

# Mock the response to avoid making real HTTP requests
# Create mock HTTP client and inject it directly into the logger
mock_response = mock.Mock(spec=Response)
mock_response.raise_for_status.return_value = None

mock_response.status_code = 200
mock_response.json.return_value = {"id": "test_email_id"}
mock_httpx_client.post.return_value = mock_response
mock_response.raise_for_status.return_value = None

mock_async_client = mock.AsyncMock()
mock_async_client.post.return_value = mock_response

# Directly inject the mock client to bypass any caching
logger.async_httpx_client = mock_async_client

# Send email
await logger.send_email(
from_email=from_email, to_email=to_email, subject=subject, html_body=html_body
)

# Verify the HTTP client was called with multiple recipients
mock_httpx_client.post.assert_called_once()
call_args = mock_httpx_client.post.call_args
mock_async_client.post.assert_called_once()
call_args = mock_async_client.post.call_args
request_body = call_args[1]["json"]
assert request_body["to"] == to_email
6 changes: 2 additions & 4 deletions tests/test_litellm/google_genai/test_google_genai_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -183,10 +183,8 @@ def test_stream_transformation_error_sync():
"translate_completion_output_params_streaming",
return_value=None
):
# Mock litellm.completion at the module level where it's imported
# We need to patch it in the handler module, not in litellm itself
with patch("litellm.google_genai.adapters.handler.litellm") as mock_litellm:
mock_litellm.completion.return_value = mock_stream
# Patch litellm.completion directly to prevent real API calls
with patch("litellm.completion", return_value=mock_stream):
# Call the handler with stream=True and expect a ValueError
with pytest.raises(ValueError, match="Failed to transform streaming response"):
GenerateContentToCompletionHandler.generate_content_handler(
Expand Down
7 changes: 5 additions & 2 deletions tests/test_litellm/test_main.py
Original file line number Diff line number Diff line change
Expand Up @@ -1335,6 +1335,8 @@ def capture_completion(**kwargs):


def test_image_edit_merges_headers_and_extra_headers():
from litellm.images.main import base_llm_http_handler

combined_headers = {
"x-test-header-one": "value-1",
"x-test-header-two": "value-2",
Expand All @@ -1351,8 +1353,9 @@ def test_image_edit_merges_headers_and_extra_headers():
"litellm.images.main.ProviderConfigManager.get_provider_image_edit_config",
return_value=mock_image_edit_config,
) as mock_config,
patch(
"litellm.images.main.base_llm_http_handler.image_edit_handler",
patch.object(
base_llm_http_handler,
"image_edit_handler",
return_value="ok",
) as mock_handler,
):
Expand Down
Loading
Loading