mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-09 03:18:44 +00:00
Fix broken mocks in 6 flaky tests to prevent real API calls (#19829)
* Fix broken mocks in 6 flaky tests to prevent real API calls
Added network-level HTTP blocking using respx to prevent tests from making real API calls when Python-level mocks fail. This makes tests more reliable and retryable in CI.
Changes:
- Azure OIDC test: Added Azure Identity SDK mock to prevent real Azure calls
- Vector store test: Added @respx.mock decorator to block HTTP requests
- Resend email tests (3): Added @respx.mock decorator for all 3 test functions
- SendGrid email test: Added @respx.mock decorator
All test assertions and verification logic remain unchanged - only added safety nets to catch leaked API calls.
* Fix failing OIDC secret manager tests
Fixed two test failures in test_secret_managers_main.py:
1. test_oidc_azure_ad_token_success: Corrected the patch path for get_bearer_token_provider from 'litellm.secret_managers.get_azure_ad_token_provider.get_bearer_token_provider' to 'azure.identity.get_bearer_token_provider' since the function is imported from azure.identity.
2. test_oidc_google_success: Added @patch('httpx.Client') decorator to prevent any real HTTP connections during test execution, resolving httpx.ConnectError issues.
Both tests now pass successfully.
This commit is contained in:
parent
c442fcd922
commit
f95572e3ed
4 changed files with 48 additions and 9 deletions
|
|
@ -2,7 +2,9 @@ import os
|
||||||
import sys
|
import sys
|
||||||
import unittest.mock as mock
|
import unittest.mock as mock
|
||||||
|
|
||||||
|
import httpx
|
||||||
import pytest
|
import pytest
|
||||||
|
import respx
|
||||||
from httpx import Response
|
from httpx import Response
|
||||||
|
|
||||||
sys.path.insert(0, os.path.abspath("../../.."))
|
sys.path.insert(0, os.path.abspath("../../.."))
|
||||||
|
|
@ -39,7 +41,13 @@ def mock_httpx_client():
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
|
@respx.mock
|
||||||
async def test_send_email_success(mock_env_vars, mock_httpx_client):
|
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"})
|
||||||
|
)
|
||||||
|
|
||||||
# Initialize the logger
|
# Initialize the logger
|
||||||
logger = ResendEmailLogger()
|
logger = ResendEmailLogger()
|
||||||
|
|
||||||
|
|
@ -73,7 +81,13 @@ async def test_send_email_success(mock_env_vars, mock_httpx_client):
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
|
@respx.mock
|
||||||
async def test_send_email_missing_api_key(mock_httpx_client):
|
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"})
|
||||||
|
)
|
||||||
|
|
||||||
# Remove the API key from environment before initializing logger
|
# Remove the API key from environment before initializing logger
|
||||||
original_key = os.environ.pop("RESEND_API_KEY", None)
|
original_key = os.environ.pop("RESEND_API_KEY", None)
|
||||||
|
|
||||||
|
|
@ -111,7 +125,13 @@ async def test_send_email_missing_api_key(mock_httpx_client):
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
|
@respx.mock
|
||||||
async def test_send_email_multiple_recipients(mock_env_vars, mock_httpx_client):
|
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"})
|
||||||
|
)
|
||||||
|
|
||||||
# Initialize the logger
|
# Initialize the logger
|
||||||
logger = ResendEmailLogger()
|
logger = ResendEmailLogger()
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,9 @@ import os
|
||||||
import sys
|
import sys
|
||||||
import unittest.mock as mock
|
import unittest.mock as mock
|
||||||
|
|
||||||
|
import httpx
|
||||||
import pytest
|
import pytest
|
||||||
|
import respx
|
||||||
from httpx import Response
|
from httpx import Response
|
||||||
|
|
||||||
sys.path.insert(0, os.path.abspath("../../.."))
|
sys.path.insert(0, os.path.abspath("../../.."))
|
||||||
|
|
@ -101,7 +103,13 @@ async def test_send_email_missing_api_key():
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
|
@respx.mock
|
||||||
async def test_send_email_multiple_recipients(mock_env_vars, mock_httpx_client):
|
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.sendgrid.com/v3/mail/send").mock(
|
||||||
|
return_value=httpx.Response(202, text="accepted")
|
||||||
|
)
|
||||||
|
|
||||||
logger = SendGridEmailLogger()
|
logger = SendGridEmailLogger()
|
||||||
|
|
||||||
from_email = "test@example.com"
|
from_email = "test@example.com"
|
||||||
|
|
|
||||||
|
|
@ -48,7 +48,8 @@ def mock_env():
|
||||||
|
|
||||||
@patch("litellm.secret_managers.main.oidc_cache")
|
@patch("litellm.secret_managers.main.oidc_cache")
|
||||||
@patch("litellm.secret_managers.main._get_oidc_http_handler")
|
@patch("litellm.secret_managers.main._get_oidc_http_handler")
|
||||||
def test_oidc_google_success(mock_get_http_handler, mock_oidc_cache):
|
@patch("httpx.Client") # Prevent any real HTTP connections
|
||||||
|
def test_oidc_google_success(mock_httpx_client, mock_get_http_handler, mock_oidc_cache):
|
||||||
mock_oidc_cache.get_cache.return_value = None
|
mock_oidc_cache.get_cache.return_value = None
|
||||||
mock_handler = MockHTTPHandler(timeout=600.0)
|
mock_handler = MockHTTPHandler(timeout=600.0)
|
||||||
mock_get_http_handler.return_value = mock_handler
|
mock_get_http_handler.return_value = mock_handler
|
||||||
|
|
@ -156,8 +157,14 @@ def test_oidc_azure_ad_token_success(mock_get_azure_ad_token_provider):
|
||||||
if "AZURE_FEDERATED_TOKEN_FILE" in os.environ:
|
if "AZURE_FEDERATED_TOKEN_FILE" in os.environ:
|
||||||
del os.environ["AZURE_FEDERATED_TOKEN_FILE"]
|
del os.environ["AZURE_FEDERATED_TOKEN_FILE"]
|
||||||
|
|
||||||
|
# Mock the token provider function that gets returned and called
|
||||||
mock_token_provider = Mock(return_value="azure_ad_token")
|
mock_token_provider = Mock(return_value="azure_ad_token")
|
||||||
mock_get_azure_ad_token_provider.return_value = mock_token_provider
|
mock_get_azure_ad_token_provider.return_value = mock_token_provider
|
||||||
|
|
||||||
|
# Also mock the Azure Identity SDK to prevent any real Azure calls
|
||||||
|
with patch("azure.identity.get_bearer_token_provider") as mock_bearer:
|
||||||
|
mock_bearer.return_value = mock_token_provider
|
||||||
|
|
||||||
secret_name = "oidc/azure/api://azure-audience"
|
secret_name = "oidc/azure/api://azure-audience"
|
||||||
result = get_secret(secret_name)
|
result = get_secret(secret_name)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -119,8 +119,12 @@ def test_add_vector_store_to_registry():
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@respx.mock
|
||||||
def test_search_uses_registry_credentials():
|
def test_search_uses_registry_credentials():
|
||||||
"""search() should pull credentials from vector_store_registry when available"""
|
"""search() should pull credentials from vector_store_registry when available"""
|
||||||
|
# Block all HTTP requests at the network level to prevent real API calls
|
||||||
|
respx.route().mock(return_value=httpx.Response(200, json={"object": "list", "data": []}))
|
||||||
|
|
||||||
vector_store = LiteLLM_ManagedVectorStore(
|
vector_store = LiteLLM_ManagedVectorStore(
|
||||||
vector_store_id="vs1",
|
vector_store_id="vs1",
|
||||||
custom_llm_provider="bedrock",
|
custom_llm_provider="bedrock",
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue