test(aiohttp): assert the exception mapping and ssl override on the real request path

This commit is contained in:
mateo-berri 2026-08-28 12:21:24 -07:00
parent 5360439e14
commit d366023cf0
2 changed files with 34 additions and 63 deletions

View file

@ -1,6 +1,6 @@
{
"TQ001": {
"limit": 736
"limit": 733
},
"TQ002": {
"limit": 742

View file

@ -1,93 +1,64 @@
import asyncio
import copy
import time
from datetime import datetime
from unittest import mock
import socket
from typing import Final
import aiohttp
import httpx
from aiohttp import ClientSession
from dotenv import load_dotenv
from litellm.types.utils import StandardCallbackDynamicParams
load_dotenv()
import pytest
from aiohttp import ClientSession
import litellm
from litellm.llms.custom_httpx.aiohttp_transport import LiteLLMAiohttpTransport
from litellm.llms.custom_httpx.http_handler import AsyncHTTPHandler
@pytest.mark.asyncio
async def test_client_session_helper():
"""Test that the client session helper handles event loop changes correctly"""
transport = AsyncHTTPHandler._create_aiohttp_transport()
assert isinstance(transport, LiteLLMAiohttpTransport)
def _closed_local_port() -> int:
with socket.socket() as probe:
probe.bind(("127.0.0.1", 0))
return probe.getsockname()[1]
session1 = transport._get_valid_client_session()
async def test_client_session_helper() -> None:
transport: Final = AsyncHTTPHandler._create_aiohttp_transport()
assert isinstance(transport, LiteLLMAiohttpTransport)
session1: Final = transport._get_valid_client_session()
assert isinstance(session1, ClientSession)
assert session1.closed is False
assert getattr(session1, "_loop") is asyncio.get_running_loop()
# Within the same event loop the valid session is reused, not rebuilt
session2 = transport._get_valid_client_session()
session2: Final = transport._get_valid_client_session()
assert session2 is session1
await session1.close()
async def test_event_loop_robustness():
"""Test behavior when event loops change (simulating CI/CD scenario)"""
transport = AsyncHTTPHandler._create_aiohttp_transport()
session = transport._get_valid_client_session()
async def test_event_loop_robustness() -> None:
transport: Final = AsyncHTTPHandler._create_aiohttp_transport()
session: Final = transport._get_valid_client_session()
assert isinstance(session, ClientSession)
# A closed session must be replaced with a live one bound to this loop
await session.close()
session_after_close = transport._get_valid_client_session()
session_after_close: Final = transport._get_valid_client_session()
assert isinstance(session_after_close, ClientSession)
assert session_after_close is not session
assert session_after_close.closed is False
# A client that is a factory rather than a session must also be rebuilt
transport.client = lambda: ClientSession() # type: ignore[assignment]
session_after_factory = transport._get_valid_client_session()
transport.client = lambda: ClientSession()
session_after_factory: Final = transport._get_valid_client_session()
assert isinstance(session_after_factory, ClientSession)
assert session_after_factory is not session_after_close
assert session_after_factory.closed is False
assert transport.client is session_after_factory
await session_after_close.close()
await session_after_factory.close()
async def test_httpx_request_simulation():
"""Test that the transport can handle a simulated HTTP request"""
transport = AsyncHTTPHandler._create_aiohttp_transport(ssl_verify=False)
request = httpx.Request("GET", "https://httpbin.org/headers")
# The per-request SSL override the request path reads must reflect ssl_verify
assert transport._ssl_verify is False
session = transport._get_valid_client_session()
assert isinstance(session, ClientSession)
assert session.closed is False
assert callable(session.request)
assert session.connector is not None
assert session.connector._ssl is False
with mock.patch.object(
transport, "_make_aiohttp_request", new=mock.AsyncMock(side_effect=RuntimeError("boom"))
) as mocked_request:
with pytest.raises(RuntimeError):
@pytest.mark.parametrize(("ssl_verify", "expected_ssl"), [(False, False), (None, True)])
async def test_refused_connection_maps_to_httpx_connect_error(ssl_verify: bool | None, expected_ssl: bool) -> None:
transport: Final = AsyncHTTPHandler._create_aiohttp_transport(ssl_verify=ssl_verify)
port: Final = _closed_local_port()
request: Final = httpx.Request("GET", f"https://127.0.0.1:{port}/")
try:
with pytest.raises(httpx.ConnectError) as raised:
await transport.handle_async_request(request)
assert mocked_request.call_count == 1
call_kwargs = mocked_request.call_args.kwargs
assert call_kwargs["request"] is request
assert call_kwargs["ssl_verify"] is False
assert call_kwargs["client_session"] is session
await session.close()
finally:
await transport._get_valid_client_session().close()
cause: Final = raised.value.__cause__
assert isinstance(cause, aiohttp.ClientConnectorError)
assert cause.ssl is expected_ssl
assert (cause.host, cause.port) == ("127.0.0.1", port)