""" Tests for HTTP client. """ import pytest from unittest.mock import AsyncMock, patch, MagicMock import httpx from core.clients.http_client import HTTPClient @pytest.fixture def mock_httpx_response(): """Create a mock httpx response.""" response = MagicMock(spec=httpx.Response) response.status_code = 200 response.json.return_value = {"success": True, "data": {"id": 1}} response.elapsed.total_seconds.return_value = 0.123 response.raise_for_status = MagicMock() return response @pytest.mark.asyncio async def test_http_client_initialization(): """Test HTTP client initialization.""" client = HTTPClient( base_url="https://api.example.com", timeout=30.0, max_retries=3, headers={"Authorization": "Bearer test_token"} ) assert client.base_url == "https://api.example.com" assert client.timeout == 30.0 assert client.max_retries == 3 assert client.default_headers["Authorization"] == "Bearer test_token" await client.close() @pytest.mark.asyncio async def test_http_client_get_request(mock_httpx_response): """Test HTTP GET request.""" client = HTTPClient(base_url="https://api.example.com") with patch.object(client, '_get_client') as mock_get_client: mock_async_client = AsyncMock() mock_async_client.request = AsyncMock(return_value=mock_httpx_response) mock_get_client.return_value = mock_async_client response = await client.get("/users/1", params={"include": "profile"}) assert response.status_code == 200 data = response.json() assert data["success"] is True # Verify the request was made correctly mock_async_client.request.assert_called_once() call_args = mock_async_client.request.call_args assert call_args.kwargs["method"] == "GET" assert "users/1" in call_args.kwargs["url"] assert call_args.kwargs["params"] == {"include": "profile"} await client.close() @pytest.mark.asyncio async def test_http_client_post_request(mock_httpx_response): """Test HTTP POST request.""" client = HTTPClient(base_url="https://api.example.com") with patch.object(client, '_get_client') as mock_get_client: mock_async_client = AsyncMock() mock_async_client.request = AsyncMock(return_value=mock_httpx_response) mock_get_client.return_value = mock_async_client payload = {"name": "John", "email": "john@example.com"} response = await client.post("/users", json=payload) assert response.status_code == 200 # Verify the request mock_async_client.request.assert_called_once() call_args = mock_async_client.request.call_args assert call_args.kwargs["method"] == "POST" assert call_args.kwargs["json"] == payload await client.close() @pytest.mark.asyncio async def test_http_client_context_manager(): """Test HTTP client as context manager.""" async with HTTPClient(base_url="https://api.example.com") as client: assert client is not None assert client._client is None # Not initialized until first use # Client should be closed after context assert client._client is None @pytest.mark.asyncio async def test_http_client_retry_on_timeout(): """Test automatic retry on timeout.""" client = HTTPClient( base_url="https://api.example.com", max_retries=3, retry_delay=0.01 # Fast retry for testing ) with patch.object(client, '_get_client') as mock_get_client: mock_async_client = AsyncMock() # Simulate 2 timeouts, then success mock_async_client.request = AsyncMock( side_effect=[ httpx.TimeoutException("Timeout 1"), httpx.TimeoutException("Timeout 2"), MagicMock( status_code=200, json=MagicMock(return_value={"success": True}), elapsed=MagicMock(total_seconds=MagicMock(return_value=0.1)), raise_for_status=MagicMock() ) ] ) mock_get_client.return_value = mock_async_client response = await client.get("/users") assert response.status_code == 200 assert mock_async_client.request.call_count == 3 # 2 retries + 1 success await client.close() @pytest.mark.asyncio async def test_http_client_retry_exhausted(): """Test retry exhaustion.""" client = HTTPClient( base_url="https://api.example.com", max_retries=2, retry_delay=0.01 ) with patch.object(client, '_get_client') as mock_get_client: mock_async_client = AsyncMock() mock_async_client.request = AsyncMock( side_effect=httpx.TimeoutException("Persistent timeout") ) mock_get_client.return_value = mock_async_client with pytest.raises(httpx.TimeoutException): await client.get("/users") assert mock_async_client.request.call_count == 2 # max_retries await client.close() @pytest.mark.asyncio async def test_http_client_custom_timeout(): """Test custom timeout for individual request.""" client = HTTPClient(base_url="https://api.example.com", timeout=30.0) with patch.object(client, '_get_client') as mock_get_client: mock_async_client = AsyncMock() mock_async_client.request = AsyncMock( return_value=MagicMock( status_code=200, json=MagicMock(return_value={}), elapsed=MagicMock(total_seconds=MagicMock(return_value=0.1)), raise_for_status=MagicMock() ) ) mock_get_client.return_value = mock_async_client await client.get("/users", timeout=10.0) # Verify custom timeout was used call_args = mock_async_client.request.call_args assert call_args.kwargs["timeout"] == 10.0 await client.close() @pytest.mark.asyncio async def test_http_client_headers_merge(): """Test header merging.""" client = HTTPClient( base_url="https://api.example.com", headers={"Authorization": "Bearer token", "User-Agent": "TestClient/1.0"} ) with patch.object(client, '_get_client') as mock_get_client: mock_async_client = AsyncMock() mock_async_client.request = AsyncMock( return_value=MagicMock( status_code=200, json=MagicMock(return_value={}), elapsed=MagicMock(total_seconds=MagicMock(return_value=0.1)), raise_for_status=MagicMock() ) ) mock_get_client.return_value = mock_async_client await client.get("/users", headers={"X-Custom": "value"}) # Verify headers were merged call_args = mock_async_client.request.call_args headers = call_args.kwargs["headers"] assert headers["Authorization"] == "Bearer token" assert headers["User-Agent"] == "TestClient/1.0" assert headers["X-Custom"] == "value" await client.close()