From add0ff19d34a6bd80223a115f9998431b626afca Mon Sep 17 00:00:00 2001 From: Yaniv Israel Date: Sun, 21 Jun 2026 17:02:18 +0300 Subject: [PATCH] fix(test): replace live httpbin.org call with mocked transport in test_pass_through_with_httpbin_redirect Root cause of OOM: the test made a real HTTP request to https://httpbin.org inside a pytest-xdist worker. Under memory pressure the worker's httpx client and redirect-following logic allocated enough virtual memory to trip the OOM killer (confirmed by ulimit -v 16GB reproducing the crash with 'node down: Not properly terminated' on this exact test). Fix: replace the real network call with a custom httpx.AsyncBaseTransport that returns a pre-built 302 -> 200 response sequence in-memory. The test now runs hermetically with no network dependency and no excess memory allocation. ulimit -v 16GB: 24,284 passed (0 crashes) after this fix. --- .../test_pass_through_endpoints.py | 63 ++++++++++++------- 1 file changed, 40 insertions(+), 23 deletions(-) diff --git a/tests/test_litellm/proxy/pass_through_endpoints/test_pass_through_endpoints.py b/tests/test_litellm/proxy/pass_through_endpoints/test_pass_through_endpoints.py index 362f4986c62..35abf261be5 100644 --- a/tests/test_litellm/proxy/pass_through_endpoints/test_pass_through_endpoints.py +++ b/tests/test_litellm/proxy/pass_through_endpoints/test_pass_through_endpoints.py @@ -2198,10 +2198,11 @@ async def test_pass_through_request_query_params_forwarding(): @pytest.mark.asyncio async def test_pass_through_with_httpbin_redirect(): """ - Integration test using httpbin.org redirect endpoint to test real redirect handling. - This tests the actual redirect handling capability end-to-end using the full pass_through_request function. + Tests redirect handling in pass_through_request using a mocked HTTP transport. + The mock simulates: GET /redirect/1 -> 302 Location: /get -> 200 {url: ...} + No real network calls are made. """ - from unittest.mock import MagicMock + import json from fastapi import Request from starlette.datastructures import Headers, QueryParams @@ -2210,24 +2211,50 @@ async def test_pass_through_with_httpbin_redirect(): pass_through_request, ) - # Create mock request + # Build the two responses the mock transport will return in order: + # 1. 302 redirect from /redirect/1 -> /get + redirect_response = httpx.Response( + status_code=302, + headers={"location": "https://httpbin.org/get"}, + content=b"", + request=httpx.Request("GET", "https://httpbin.org/redirect/1"), + ) + # 2. 200 final response from /get + final_body = json.dumps({"url": "https://httpbin.org/get"}).encode() + final_response = httpx.Response( + status_code=200, + headers={"content-type": "application/json"}, + content=final_body, + request=httpx.Request("GET", "https://httpbin.org/get"), + ) + + responses = iter([redirect_response, final_response]) + + class _MockTransport(httpx.AsyncBaseTransport): + async def handle_async_request(self, request: httpx.Request) -> httpx.Response: + return next(responses) + + # pass_through_request accesses get_async_httpx_client(...).client, so the + # mock must expose a .client attribute wrapping our transport. + mock_handler = MagicMock() + mock_handler.client = httpx.AsyncClient(transport=_MockTransport(), follow_redirects=True) + + # Create mock FastAPI request mock_request = MagicMock(spec=Request) mock_request.method = "GET" mock_request.headers = Headers({}) mock_request.query_params = QueryParams("") - # Mock the body method to return empty bytes for GET request async def mock_body(): return b"" mock_request.body = mock_body - - # Mock user API key dict mock_user_api_key_dict = MagicMock() - try: - # Test with httpbin.org redirect endpoint - # This will redirect to httpbin.org/get + with patch( + "litellm.proxy.pass_through_endpoints.pass_through_endpoints.get_async_httpx_client", + return_value=mock_handler, + ): response = await pass_through_request( request=mock_request, target="https://httpbin.org/redirect/1", @@ -2235,19 +2262,9 @@ async def test_pass_through_with_httpbin_redirect(): user_api_key_dict=mock_user_api_key_dict, ) - # Should get the final response (200) from /get endpoint, not the redirect (302) - assert response.status_code == 200 - - # The response should be from the /get endpoint - response_content = bytes(response.body).decode("utf-8") - - # httpbin.org/get returns JSON with info about the request - assert '"url": "https://httpbin.org/get"' in response_content - except Exception as e: - # If httpbin.org is not accessible, skip the test - import pytest - - pytest.skip(f"Could not reach httpbin.org for integration test: {e}") + assert response.status_code == 200 + response_content = bytes(response.body).decode("utf-8") + assert '"url": "https://httpbin.org/get"' in response_content @pytest.mark.asyncio