"""Unit tests for HTTP client."""
from unittest.mock import AsyncMock, MagicMock, patch
import httpx
import pytest
from core.client import SerpClient
from core.exceptions import SerpAPIError, SerpAuthError, SerpTimeoutError
@pytest.fixture
def client():
"""Create a client instance for testing."""
return SerpClient(api_token="test-token", base_url="https://api.test.com")
class TestSerpClient:
"""Tests for SerpClient class."""
def test_init_with_params(self):
"""Test client initialization with explicit parameters."""
client = SerpClient(api_token="my-token", base_url="https://custom.api.com")
assert client.api_token == "my-token"
assert client.base_url == "https://custom.api.com"
def test_get_headers(self, client):
"""Test that headers are correctly generated."""
headers = client._get_headers()
assert headers["accept"] == "application/json"
assert headers["authorization"] == "Bearer test-token"
assert headers["content-type"] == "application/json"
def test_get_headers_no_token(self):
"""Test that missing token raises auth error."""
client = SerpClient(api_token="", base_url="https://api.test.com")
with pytest.raises(SerpAuthError, match="not configured"):
client._get_headers()
@pytest.mark.asyncio
async def test_request_success(self, client, mock_search_response):
"""Test successful API request."""
mock_response = MagicMock()
mock_response.status_code = 200
mock_response.json.return_value = mock_search_response
with patch("httpx.AsyncClient") as mock_client:
mock_instance = AsyncMock()
mock_instance.post.return_value = mock_response
mock_client.return_value.__aenter__.return_value = mock_instance
result = await client.request("/serp/google", {"query": "test"})
assert result == mock_search_response
@pytest.mark.asyncio
async def test_request_auth_error_401(self, client):
"""Test 401 response raises auth error."""
mock_response = MagicMock()
mock_response.status_code = 401
mock_response.raise_for_status.side_effect = httpx.HTTPStatusError(
"Unauthorized", request=MagicMock(), response=mock_response
)
with patch("httpx.AsyncClient") as mock_client:
mock_instance = AsyncMock()
mock_instance.post.return_value = mock_response
mock_client.return_value.__aenter__.return_value = mock_instance
with pytest.raises(SerpAuthError, match="Invalid API token"):
await client.request("/serp/google", {})
@pytest.mark.asyncio
async def test_request_timeout(self, client):
"""Test timeout raises timeout error."""
with patch("httpx.AsyncClient") as mock_client:
mock_instance = AsyncMock()
mock_instance.post.side_effect = httpx.TimeoutException("Timeout")
mock_client.return_value.__aenter__.return_value = mock_instance
with pytest.raises(SerpTimeoutError, match="timed out"):
await client.request("/serp/google", {})
@pytest.mark.asyncio
async def test_request_http_error(self, client):
"""Test HTTP error raises API error."""
mock_response = MagicMock()
mock_response.status_code = 500
mock_response.text = "Internal Server Error"
mock_response.raise_for_status.side_effect = httpx.HTTPStatusError(
"Error", request=MagicMock(), response=mock_response
)
with patch("httpx.AsyncClient") as mock_client:
mock_instance = AsyncMock()
mock_instance.post.return_value = mock_response
mock_client.return_value.__aenter__.return_value = mock_instance
with pytest.raises(SerpAPIError) as exc_info:
await client.request("/serp/google", {})
assert exc_info.value.status_code == 500
@pytest.mark.asyncio
async def test_search_method(self, client, mock_search_response):
"""Test the search convenience method."""
mock_response = MagicMock()
mock_response.status_code = 200
mock_response.json.return_value = mock_search_response
with patch("httpx.AsyncClient") as mock_client:
mock_instance = AsyncMock()
mock_instance.post.return_value = mock_response
mock_client.return_value.__aenter__.return_value = mock_instance
result = await client.search(query="test query", type="search")
assert result == mock_search_response
# Verify the correct endpoint was called
call_args = mock_instance.post.call_args
assert "/serp/google" in call_args[0][0]