fix: make HTTPHandler mockable in OIDC secret manager tests (#19803)

* fix: make HTTPHandler mockable in OIDC secret manager tests

- Add _get_oidc_http_handler() factory function to make HTTPHandler
  easily mockable in tests
- Update test_oidc_github_success to patch factory function instead
  of HTTPHandler directly
- Update Google OIDC tests for consistency
- Fixes test_oidc_github_success failure where mock was bypassed

This change allows tests to properly mock HTTPHandler instances used
for OIDC token requests, fixing the test failure where the mock was
not being used.

* fix: patch base_llm_http_handler method directly in container tests

- Use patch.object to patch container_create_handler method directly
  on the base_llm_http_handler instance instead of patching the module
- Fixes test_provider_support[openai] failure where mock wasn't applied
- Also fixes test_error_handling_integration with same approach

The issue was that patching 'litellm.containers.main.base_llm_http_handler'
didn't work because the module imports it with 'from litellm.main import',
creating a local reference. Using patch.object patches the method on the
actual object instance, which works regardless of import style.

* fix: resolve flaky test_openai_env_base by clearing cache

- Add cache clearing at start of test_openai_env_base to prevent cache pollution
- Ensures no cached clients from previous tests interfere with respx mocks
- Fixes intermittent failures where aiohttp transport was used instead of httpx
- Test-only change with low risk, no production code modifications

Resolves flaky test marked with @pytest.mark.flaky(retries=3, delay=1)
Both parametrized versions (OPENAI_API_BASE and OPENAI_BASE_URL) now pass consistently

* test: add explicit mock verification in test_provider_support

- Capture mock handler with 'as mock_handler' for explicit validation
- Add assert_called_once() to verify mock was actually used
- Ensures test verifies no real API calls are made
- Follows same pattern as test_openai_env_base validation
This commit is contained in:
Alexsander Hamir 2026-01-26 11:00:42 -08:00 • committed by GitHub
parent a8e72950db
commit f2c3a01a57
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 45 additions and 23 deletions

View file

@ -17,6 +17,22 @@ from litellm.secret_managers.secret_manager_handler import get_secret_from_manag
oidc_cache = DualCache()
def _get_oidc_http_handler(timeout: Optional[httpx.Timeout] = None) -> HTTPHandler:
"""
Factory function to create HTTPHandler for OIDC requests.
This function can be mocked in tests.
Args:
timeout: Optional timeout for HTTP requests. Defaults to 600.0 seconds with 5.0 connect timeout.
Returns:
HTTPHandler instance configured for OIDC requests.
"""
if timeout is None:
timeout = httpx.Timeout(timeout=600.0, connect=5.0)
return HTTPHandler(timeout=timeout)
######### Secret Manager ############################
# checks if user has passed in a secret manager client
# if passed in then checks the secret there
@ -103,7 +119,7 @@ def get_secret( # noqa: PLR0915
if oidc_token is not None:
return oidc_token
oidc_client = HTTPHandler(timeout=httpx.Timeout(timeout=600.0, connect=5.0))
oidc_client = _get_oidc_http_handler()
# https://cloud.google.com/compute/docs/instances/verifying-instance-identity#request_signature
response = oidc_client.get(
"http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/identity",
@ -141,7 +157,7 @@ def get_secret( # noqa: PLR0915
if oidc_token is not None:
return oidc_token
oidc_client = HTTPHandler(timeout=httpx.Timeout(timeout=600.0, connect=5.0))
oidc_client = _get_oidc_http_handler()
response = oidc_client.get(
actions_id_token_request_url,
params={"audience": oidc_aud},

View file

@ -357,15 +357,15 @@ class TestContainerIntegration:
def test_error_handling_integration(self):
"""Test error handling in the integration flow."""
with patch('litellm.containers.main.base_llm_http_handler') as mock_handler:
# Simulate an API error
mock_handler.container_create_handler.side_effect = litellm.APIError(
status_code=400,
message="API Error occurred",
llm_provider="openai",
model=""
)
# Simulate an API error
api_error = litellm.APIError(
status_code=400,
message="API Error occurred",
llm_provider="openai",
model=""
)
with patch.object(litellm.main.base_llm_http_handler, 'container_create_handler', side_effect=api_error):
with pytest.raises(litellm.APIError):
create_container(
name="Error Test Container",
@ -385,12 +385,12 @@ class TestContainerIntegration:
name="Provider 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(litellm.main.base_llm_http_handler, 'container_create_handler', return_value=mock_response) as mock_handler:
response = create_container(
name="Provider Test Container",
custom_llm_provider=provider
)
assert response.name == "Provider Test Container"
# Verify the mock was actually called (not making real API calls)
mock_handler.assert_called_once()

View file

@ -47,11 +47,11 @@ def mock_env():
@patch("litellm.secret_managers.main.oidc_cache")
@patch("litellm.secret_managers.main.HTTPHandler")
def test_oidc_google_success(mock_http_handler, mock_oidc_cache):
@patch("litellm.secret_managers.main._get_oidc_http_handler")
def test_oidc_google_success(mock_get_http_handler, mock_oidc_cache):
mock_oidc_cache.get_cache.return_value = None
mock_handler = MockHTTPHandler(timeout=600.0)
mock_http_handler.return_value = mock_handler
mock_get_http_handler.return_value = mock_handler
secret_name = "oidc/google/[invalid url, do not cite]"
result = get_secret(secret_name)
@ -67,12 +67,12 @@ def test_oidc_google_cached(mock_oidc_cache):
mock_oidc_cache.get_cache.return_value = "cached_token"
secret_name = "oidc/google/[invalid url, do not cite]"
with patch("litellm.secret_managers.main.HTTPHandler") as mock_http:
with patch("litellm.secret_managers.main._get_oidc_http_handler") as mock_get_http:
result = get_secret(secret_name)
assert result == "cached_token", f"Expected cached token, got {result}"
mock_oidc_cache.get_cache.assert_called_with(key=secret_name)
mock_http.assert_not_called()
mock_get_http.assert_not_called()
@patch("litellm.secret_managers.main.oidc_cache")
@ -80,7 +80,7 @@ def test_oidc_google_failure(mock_oidc_cache):
mock_handler = MockHTTPHandler(timeout=600.0)
mock_handler.status_code = 400
with patch("litellm.secret_managers.main.HTTPHandler", return_value=mock_handler):
with patch("litellm.secret_managers.main._get_oidc_http_handler", return_value=mock_handler):
mock_oidc_cache.get_cache.return_value = None
secret_name = "oidc/google/https://example.com/api"
@ -106,13 +106,13 @@ def test_oidc_circleci_failure(monkeypatch):
@patch("litellm.secret_managers.main.oidc_cache")
@patch("litellm.secret_managers.main.HTTPHandler")
def test_oidc_github_success(mock_http_handler, mock_oidc_cache, mock_env):
@patch("litellm.secret_managers.main._get_oidc_http_handler")
def test_oidc_github_success(mock_get_http_handler, mock_oidc_cache, mock_env):
mock_env["ACTIONS_ID_TOKEN_REQUEST_URL"] = "https://github.com/token"
mock_env["ACTIONS_ID_TOKEN_REQUEST_TOKEN"] = "github_token"
mock_oidc_cache.get_cache.return_value = None
mock_handler = MockHTTPHandler(timeout=600.0)
mock_http_handler.return_value = mock_handler
mock_get_http_handler.return_value = mock_handler
secret_name = "oidc/github/github-audience"
result = get_secret(secret_name)

View file

@ -477,6 +477,12 @@ async def test_openai_env_base(
respx_mock: respx.MockRouter, env_base, openai_api_response, monkeypatch
):
"This tests OpenAI env variables are honored, including legacy OPENAI_API_BASE"
# Clear cache to ensure no cached clients from previous tests interfere
# This prevents cache pollution where a previous test cached a client with
# aiohttp transport, which would bypass respx mocks
if hasattr(litellm, "in_memory_llm_clients_cache"):
litellm.in_memory_llm_clients_cache.flush_cache()
# Ensure aiohttp transport is disabled to use httpx which respx can mock
litellm.disable_aiohttp_transport = True