"""Tests for search functionality."""
import pytest
from unittest.mock import MagicMock, patch
class TestSearchDDGS:
"""Test DuckDuckGo search functionality."""
@patch("mcp_webscout.tools.search.DDGS")
def test_search_basic(self, mock_ddgs_class):
"""Test basic search functionality."""
from mcp_webscout.tools import search_ddgs
# Mock DDGS instance
mock_ddgs = MagicMock()
mock_ddgs_class.return_value = mock_ddgs
# Mock search results
mock_results = [
{"title": "Test 1", "href": "http://example.com/1", "body": "Snippet 1"},
{"title": "Test 2", "href": "http://example.com/2", "body": "Snippet 2"},
]
mock_ddgs.text.return_value = iter(mock_results)
# Call search
results = search_ddgs("test query", max_results=5)
# Verify results
assert len(results) == 2
assert results[0]["title"] == "Test 1"
assert results[0]["url"] == "http://example.com/1"
assert results[0]["snippet"] == "Snippet 1"
@patch("mcp_webscout.tools.search.DDGS")
def test_search_empty_results(self, mock_ddgs_class):
"""Test search with empty results."""
from mcp_webscout.tools import search_ddgs
mock_ddgs = MagicMock()
mock_ddgs_class.return_value = mock_ddgs
mock_ddgs.text.return_value = iter([])
results = search_ddgs("nonexistent query")
assert results == []
@patch("mcp_webscout.tools.search.DDGS")
def test_search_missing_fields(self, mock_ddgs_class):
"""Test search with missing fields in results."""
from mcp_webscout.tools import search_ddgs
mock_ddgs = MagicMock()
mock_ddgs_class.return_value = mock_ddgs
# Results with missing fields
mock_results = [
{"title": "Test", "href": "http://example.com"},
# Missing body field
]
mock_ddgs.text.return_value = iter(mock_results)
results = search_ddgs("test")
assert len(results) == 1
assert results[0]["title"] == "Test"
assert results[0]["url"] == "http://example.com"
assert results[0]["snippet"] == "" # Default value for missing field
@patch("mcp_webscout.tools.search.DDGS")
def test_search_max_results(self, mock_ddgs_class):
"""Test search with max_results parameter."""
from mcp_webscout.tools import search_ddgs
mock_ddgs = MagicMock()
mock_ddgs_class.return_value = mock_ddgs
# Create 15 results
mock_results = [
{"title": f"Test {i}", "href": f"http://example.com/{i}", "body": f"Snippet {i}"}
for i in range(15)
]
mock_ddgs.text.return_value = iter(mock_results)
# Request only 5
results = search_ddgs("test", max_results=5)
# DDGS should handle limiting, but we should get at most 5
assert len(results) <= 15
@patch("mcp_webscout.tools.search.time.sleep", return_value=None)
@patch("mcp_webscout.tools.search.DDGS")
def test_search_retry_on_error(self, mock_ddgs_class, mock_sleep):
"""Test search retry behavior on errors."""
from mcp_webscout.tools import search_ddgs
mock_ddgs = MagicMock()
mock_ddgs_class.return_value = mock_ddgs
# First call raises an error, second succeeds
mock_ddgs.text.side_effect = [
Exception("connection error"),
iter([{"title": "Test", "href": "http://example.com", "body": "Snippet"}]),
]
results = search_ddgs("test", retries=2)
assert len(results) == 1
assert results[0]["title"] == "Test"
@patch("mcp_webscout.tools.search.time.sleep", return_value=None)
@patch("mcp_webscout.tools.search.DDGS")
def test_search_tls_error_no_retry(self, mock_ddgs_class, mock_sleep):
"""Test that TLS errors are not retried."""
from mcp_webscout.tools import search_ddgs
mock_ddgs = MagicMock()
mock_ddgs_class.return_value = mock_ddgs
# TLS error should not be retried
mock_ddgs.text.side_effect = Exception("tls handshake failed")
with pytest.raises(Exception, match="tls handshake failed"):
search_ddgs("test", retries=3)