Files
litellm/tests/llm_responses_api_testing/conftest.py
T
mateo-berri 265a94cd60 tests(vcr): force pure-httpx transport when VCR is active
litellm's default LiteLLMAiohttpTransport routes requests through aiohttp,
which sits below httpx and is invisible to vcrpy's httpx-stub interception.
Under vcrpy + aiohttp, requests reach the real network but responses come
back through the stubbed httpx transport as empty 200s, surfacing as
'Unable to get json response - Expecting value: line 1 column 1 (char 0)'
in providers like Anthropic, Gemini, and any other path that exercises the
aiohttp transport.

Disabling the aiohttp transport when the VCR persister is registered
forces all calls through pure httpx, which vcrpy can record and replay
correctly.
2026-04-30 17:42:17 -07:00

169 lines
4.3 KiB
Python

# conftest.py
import asyncio
import importlib
import os
import sys
import pytest
sys.path.insert(
0, os.path.abspath("../..")
) # Adds the parent directory to the system path
import litellm # noqa: E402
from tests._vcr_redis_persister import ( # noqa: E402
filter_non_2xx_response,
make_redis_persister,
)
_FILTERED_REQUEST_HEADERS = (
"authorization",
"x-api-key",
"anthropic-api-key",
"anthropic-version",
"openai-api-key",
"azure-api-key",
"api-key",
"cookie",
"x-amz-security-token",
"x-amz-date",
"x-amz-content-sha256",
"amz-sdk-invocation-id",
"amz-sdk-request",
"x-goog-api-key",
"x-goog-user-project",
)
_FILTERED_RESPONSE_HEADERS = (
"set-cookie",
"x-request-id",
"request-id",
"cf-ray",
"anthropic-organization-id",
"openai-organization",
"x-amzn-requestid",
"x-amzn-trace-id",
"date",
)
def _scrub_response(response):
if not isinstance(response, dict):
return response
headers = response.get("headers") or {}
if isinstance(headers, dict):
for header in list(headers):
if header.lower() in _FILTERED_RESPONSE_HEADERS:
headers.pop(header, None)
return response
def _before_record_response(response):
return filter_non_2xx_response(_scrub_response(response))
@pytest.fixture(scope="module")
def vcr_config():
return {
"filter_headers": list(_FILTERED_REQUEST_HEADERS),
"decode_compressed_response": True,
"record_mode": "new_episodes",
"allow_playback_repeats": True,
"match_on": (
"method",
"scheme",
"host",
"port",
"path",
"query",
"body",
),
"before_record_response": _before_record_response,
}
def _vcr_disabled() -> bool:
if os.environ.get("LITELLM_VCR_DISABLE") == "1":
return True
return not any(
os.environ.get(var) for var in ("REDIS_URL", "REDIS_SSL_URL", "REDIS_HOST")
)
def pytest_recording_configure(config, vcr):
if _vcr_disabled():
return
vcr.register_persister(make_redis_persister())
# vcrpy patches httpx's transport; litellm's default AiohttpTransport
# routes around httpx and produces empty responses under the patched
# transport. Force pure-httpx transport so vcrpy can record/replay.
litellm.disable_aiohttp_transport = True
os.environ["DISABLE_AIOHTTP_TRANSPORT"] = "True"
@pytest.fixture(scope="session")
def event_loop():
try:
loop = asyncio.get_running_loop()
except RuntimeError:
loop = asyncio.new_event_loop()
yield loop
loop.close()
@pytest.fixture(scope="function", autouse=True)
def setup_and_teardown():
"""
This fixture reloads litellm before every function. To speed up testing by removing callbacks being chained.
"""
curr_dir = os.getcwd() # Get the current working directory
sys.path.insert(
0, os.path.abspath("../..")
) # Adds the project directory to the system path
import litellm
from litellm import Router
importlib.reload(litellm)
try:
if hasattr(litellm, "proxy") and hasattr(litellm.proxy, "proxy_server"):
import litellm.proxy.proxy_server
importlib.reload(litellm.proxy.proxy_server)
except Exception as e:
print(f"Error reloading litellm.proxy.proxy_server: {e}")
import asyncio
loop = asyncio.get_event_loop_policy().new_event_loop()
asyncio.set_event_loop(loop)
print(litellm)
# from litellm import Router, completion, aembedding, acompletion, embedding
yield
# Teardown code (executes after the yield point)
loop.close() # Close the loop created earlier
asyncio.set_event_loop(None) # Remove the reference to the loop
def pytest_collection_modifyitems(config, items):
if not _vcr_disabled():
for item in items:
if item.get_closest_marker("vcr") is not None:
continue
item.add_marker(pytest.mark.vcr)
custom_logger_tests = [
item for item in items if "custom_logger" in item.parent.name
]
other_tests = [item for item in items if "custom_logger" not in item.parent.name]
custom_logger_tests.sort(key=lambda x: x.name)
other_tests.sort(key=lambda x: x.name)
items[:] = custom_logger_tests + other_tests