Files
litellm/tests/search_tests/test_searchapi_search.py
T
2026-03-04 13:55:43 +05:30

271 lines
9.3 KiB
Python

"""
Tests for SearchAPI.io (Google Search) integration.
Tests the SearchAPI.io search provider implementation including:
- Request transformation
- Response transformation
- Parameter mapping
- Error handling
"""
import json
import os
import sys
from unittest.mock import MagicMock, Mock, patch
import httpx
import pytest
sys.path.insert(
0, os.path.abspath("../..")
)
from litellm.llms.searchapi.search.transformation import SearchAPIConfig
from litellm.llms.base_llm.search.transformation import SearchResponse, SearchResult
class TestSearchAPIConfig:
"""Test SearchAPI.io configuration and transformations."""
def test_ui_friendly_name(self):
"""Test that UI friendly name is returned correctly."""
config = SearchAPIConfig()
assert config.ui_friendly_name() == "SearchAPI.io (Google Search)"
def test_get_http_method(self):
"""Test that HTTP method is GET."""
config = SearchAPIConfig()
assert config.get_http_method() == "GET"
@patch("litellm.llms.searchapi.search.transformation.get_secret_str")
def test_validate_environment_with_api_key(self, mock_get_secret):
"""Test environment validation with API key."""
mock_get_secret.return_value = "test_api_key"
config = SearchAPIConfig()
headers = {}
result = config.validate_environment(headers, api_key="test_api_key")
assert result["Content-Type"] == "application/json"
@patch("litellm.llms.searchapi.search.transformation.get_secret_str")
def test_validate_environment_without_api_key(self, mock_get_secret):
"""Test environment validation without API key raises error."""
mock_get_secret.return_value = None
config = SearchAPIConfig()
headers = {}
with pytest.raises(ValueError, match="SEARCHAPI_API_KEY is not set"):
config.validate_environment(headers)
@patch("litellm.llms.searchapi.search.transformation.get_secret_str")
def test_transform_search_request_basic(self, mock_get_secret):
"""Test basic search request transformation."""
mock_get_secret.return_value = "test_api_key"
config = SearchAPIConfig()
result = config.transform_search_request(
query="test query",
optional_params={},
api_key="test_api_key"
)
assert "_searchapi_params" in result
params = result["_searchapi_params"]
assert params["engine"] == "google"
assert params["q"] == "test query"
assert params["api_key"] == "test_api_key"
@patch("litellm.llms.searchapi.search.transformation.get_secret_str")
def test_transform_search_request_with_max_results(self, mock_get_secret):
"""Test search request transformation with max_results parameter."""
mock_get_secret.return_value = "test_api_key"
config = SearchAPIConfig()
result = config.transform_search_request(
query="test query",
optional_params={"max_results": 5},
api_key="test_api_key"
)
params = result["_searchapi_params"]
assert params["num"] == 5
@patch("litellm.llms.searchapi.search.transformation.get_secret_str")
def test_transform_search_request_with_country(self, mock_get_secret):
"""Test search request transformation with country parameter."""
mock_get_secret.return_value = "test_api_key"
config = SearchAPIConfig()
result = config.transform_search_request(
query="test query",
optional_params={"country": "US"},
api_key="test_api_key"
)
params = result["_searchapi_params"]
assert params["gl"] == "us"
@patch("litellm.llms.searchapi.search.transformation.get_secret_str")
def test_transform_search_request_with_domain_filter(self, mock_get_secret):
"""Test search request transformation with domain filter."""
mock_get_secret.return_value = "test_api_key"
config = SearchAPIConfig()
result = config.transform_search_request(
query="test query",
optional_params={"search_domain_filter": ["example.com", "test.com"]},
api_key="test_api_key"
)
params = result["_searchapi_params"]
assert "site:example.com" in params["q"]
assert "site:test.com" in params["q"]
@patch("litellm.llms.searchapi.search.transformation.get_secret_str")
def test_transform_search_request_with_list_query(self, mock_get_secret):
"""Test search request transformation with list query."""
mock_get_secret.return_value = "test_api_key"
config = SearchAPIConfig()
result = config.transform_search_request(
query=["test", "query"],
optional_params={},
api_key="test_api_key"
)
params = result["_searchapi_params"]
assert params["q"] == "test query"
@patch("litellm.llms.searchapi.search.transformation.get_secret_str")
def test_get_complete_url(self, mock_get_secret):
"""Test URL construction with query parameters."""
mock_get_secret.return_value = None
config = SearchAPIConfig()
data = {
"_searchapi_params": {
"engine": "google",
"q": "test query",
"api_key": "test_key"
}
}
url = config.get_complete_url(
api_base=None,
optional_params={},
data=data
)
assert "https://www.searchapi.io/api/v1/search?" in url
assert "engine=google" in url
assert "q=test+query" in url
assert "api_key=test_key" in url
def test_transform_search_response(self):
"""Test search response transformation."""
config = SearchAPIConfig()
# Mock response
mock_response = Mock(spec=httpx.Response)
mock_response.json.return_value = {
"organic_results": [
{
"title": "Test Result 1",
"link": "https://example.com/1",
"snippet": "This is a test snippet 1",
"date": "2024-01-01"
},
{
"title": "Test Result 2",
"link": "https://example.com/2",
"snippet": "This is a test snippet 2"
}
]
}
result = config.transform_search_response(
raw_response=mock_response,
logging_obj=None
)
assert isinstance(result, SearchResponse)
assert result.object == "search"
assert len(result.results) == 2
# Check first result
assert result.results[0].title == "Test Result 1"
assert result.results[0].url == "https://example.com/1"
assert result.results[0].snippet == "This is a test snippet 1"
assert result.results[0].date == "2024-01-01"
assert result.results[0].last_updated is None
# Check second result
assert result.results[1].title == "Test Result 2"
assert result.results[1].url == "https://example.com/2"
assert result.results[1].snippet == "This is a test snippet 2"
assert result.results[1].date is None
def test_transform_search_response_empty(self):
"""Test search response transformation with no results."""
config = SearchAPIConfig()
mock_response = Mock(spec=httpx.Response)
mock_response.json.return_value = {
"organic_results": []
}
result = config.transform_search_response(
raw_response=mock_response,
logging_obj=None
)
assert isinstance(result, SearchResponse)
assert len(result.results) == 0
def test_append_domain_filters(self):
"""Test domain filter appending logic."""
config = SearchAPIConfig()
query = "test query"
domains = ["example.com", "test.com"]
result = config._append_domain_filters(query, domains)
assert "(test query)" in result
assert "site:example.com" in result
assert "site:test.com" in result
assert "OR" in result
assert "AND" in result
@pytest.mark.skipif(
os.environ.get("SEARCHAPI_API_KEY") is None,
reason="SEARCHAPI_API_KEY not set in environment"
)
class TestSearchAPIIntegration:
"""Integration tests for SearchAPI.io (requires API key)."""
def test_real_search_request(self):
"""
Test a real search request to SearchAPI.io.
This test is skipped if SEARCHAPI_API_KEY is not set.
"""
import litellm
response = litellm.search(
query="Python programming",
search_provider="searchapi",
max_results=5
)
assert response is not None
assert hasattr(response, "results")
assert len(response.results) > 0
assert all(hasattr(r, "title") for r in response.results)
assert all(hasattr(r, "url") for r in response.results)
assert all(hasattr(r, "snippet") for r in response.results)
if __name__ == "__main__":
pytest.main([__file__, "-v"])