"""Unit tests for SearXNG client.""" import json from unittest.mock import AsyncMock, Mock, patch import httpx import pytest from httpx import Response from src.models.schemas import SearchRequest from src.search.searxng import SearXNGClient, SearXNGError class TestSearXNGClient: """Test cases for SearXNGClient.""" @pytest.fixture def client(self): return SearXNGClient(base_url="http://test:8080") @pytest.mark.asyncio async def test_search_success(self, client): """Test successful search request.""" mock_response = { "results": [ { "title": "Test Result", "url": "https://example.com", "content": "Test content", "engine": "google", "score": 0.95 } ], "number_of_results": 1, "engines": ["google"] } mock_client = AsyncMock() mock_response_obj = Mock() mock_response_obj.status_code = 200 mock_response_obj.json = Mock(return_value=mock_response) mock_response_obj.text = json.dumps(mock_response) mock_response_obj.raise_for_status = Mock(return_value=None) mock_client.get.return_value = mock_response_obj with patch.object(client, '_client', mock_client): request = SearchRequest(q="test query", engines=["google"], page=1) result = await client.search(request) assert result.query == "test query" assert len(result.results) == 1 assert result.results[0].title == "Test Result" assert result.results[0].source == "google" @pytest.mark.asyncio async def test_search_http_error(self, client): """Test handling of HTTP errors.""" mock_client = AsyncMock() # Create proper HTTPStatusError with async side effect async def raise_http_error(*args, **kwargs): from httpx import Request, Response mock_request = Mock(spec=Request) mock_response = Response(status_code=404, text="Not found") raise httpx.HTTPStatusError( "Not found", request=mock_request, response=mock_response ) mock_client.get.side_effect = raise_http_error with patch.object(client, '_client', mock_client): request = SearchRequest(q="test", engines=["google"], page=1) with pytest.raises(SearXNGError) as exc_info: await client.search(request) assert "HTTP error" in str(exc_info.value) @pytest.mark.asyncio async def test_search_connection_error(self, client): """Test handling of connection errors.""" mock_client = AsyncMock() mock_client.get.side_effect = httpx.ConnectError("Connection refused") with patch.object(client, '_client', mock_client): request = SearchRequest(q="test", engines=["google"], page=1) with pytest.raises(SearXNGError) as exc_info: await client.search(request) assert "Request failed" in str(exc_info.value) @pytest.mark.asyncio async def test_search_invalid_json(self, client): """Test handling of invalid JSON response.""" mock_client = AsyncMock() mock_response = Mock() mock_response.status_code = 200 mock_response.json.side_effect = json.JSONDecodeError("test", "", 0) mock_response.text = "invalid json" mock_response.raise_for_status = Mock(return_value=None) mock_client.get.return_value = mock_response with patch.object(client, '_client', mock_client): request = SearchRequest(q="test", engines=["google"], page=1) with pytest.raises(SearXNGError) as exc_info: await client.search(request) assert "Invalid JSON" in str(exc_info.value) @pytest.mark.asyncio async def test_health_check_success(self, client): """Test successful health check.""" mock_client = AsyncMock() mock_client.get.return_value = Response(status_code=200) with patch.object(client, '_client', mock_client): result = await client.health_check() assert result is True @pytest.mark.asyncio async def test_health_check_failure(self, client): """Test failed health check.""" mock_client = AsyncMock() mock_client.get.side_effect = httpx.ConnectError("Connection refused") with patch.object(client, '_client', mock_client): result = await client.health_check() assert result is False def test_build_url(self, client): """Test URL building with parameters.""" params = {"q": "test query", "format": "json", "engines": "google,bing"} url = client._build_url(params) assert url.startswith("http://test:8080/search") assert "q=test+query" in url or "q=test%20query" in url assert "format=json" in url assert "engines=google%2Cbing" in url or "engines=google,bing" in url