MCP DuckDuckGo Search Plugin
- tests
"""
Tests for the DuckDuckGo search tools.
These tests verify that the MCP tools for DuckDuckGo search work correctly.
"""
import pytest
from unittest.mock import AsyncMock, patch, MagicMock
import json
# Import the tools module containing the MCP tools
from mcp_duckduckgo.tools import duckduckgo_web_search, duckduckgo_get_details, duckduckgo_related_searches
from mcp_duckduckgo.models import SearchResponse, DetailedResult
@pytest.mark.asyncio
async def test_duckduckgo_web_search(mock_context, mock_search_function):
"""Test the duckduckgo_web_search tool."""
# Mock the search function
with patch('mcp_duckduckgo.tools.duckduckgo_search', mock_search_function):
# Call the tool
result = await duckduckgo_web_search(
query="test query",
count=5,
page=1,
site=None,
time_period=None,
ctx=mock_context
)
# Verify the result
assert isinstance(result, SearchResponse)
assert len(result.results) == 2 # Based on the sample data in conftest.py
assert result.total_results == 2
assert result.page == 1
# Check the first result
first_result = result.results[0]
assert first_result.title == "Example Page 1"
assert first_result.url == "https://example.com/page1"
assert first_result.description == "This is a description for Example Page 1"
# Domain is included in result dict but not as a property in SearchResult model
# assert first_result.domain == "example.com"
@pytest.mark.asyncio
async def test_duckduckgo_web_search_with_site_filter(mock_context, mock_search_function):
"""Test the duckduckgo_web_search tool with site filter."""
# Mock the search function to capture params
mock_search_params = None
async def capture_params(params, ctx):
nonlocal mock_search_params
mock_search_params = params
return await mock_search_function(params, ctx)
with patch('mcp_duckduckgo.tools.duckduckgo_search', capture_params):
# Call the tool with site filter
await duckduckgo_web_search(
query="test query",
count=5,
page=1,
site="example.com",
time_period=None,
ctx=mock_context
)
# Verify that site filter was correctly applied to the query
assert mock_search_params is not None
assert mock_search_params["query"] == "test query site:example.com"
@pytest.mark.asyncio
async def test_duckduckgo_web_search_with_time_filter(mock_context, mock_search_function):
"""Test the duckduckgo_web_search tool with time filter."""
# Mock the search function to capture params
mock_search_params = None
async def capture_params(params, ctx):
nonlocal mock_search_params
mock_search_params = params
return await mock_search_function(params, ctx)
with patch('mcp_duckduckgo.tools.duckduckgo_search', capture_params):
# Call the tool with time filter
await duckduckgo_web_search(
query="test query",
count=5,
page=1,
site=None,
time_period="week",
ctx=mock_context
)
# Verify that time filter was correctly applied
assert mock_search_params is not None
assert "date:w" in mock_search_params["query"]
@pytest.mark.asyncio
async def test_duckduckgo_web_search_pagination(mock_context, mock_search_function):
"""Test the duckduckgo_web_search tool with pagination."""
# Mock the search function to capture params
mock_search_params = None
async def capture_params(params, ctx):
nonlocal mock_search_params
mock_search_params = params
return await mock_search_function(params, ctx)
with patch('mcp_duckduckgo.tools.duckduckgo_search', capture_params):
# Call the tool with pagination
await duckduckgo_web_search(
query="test query",
count=5,
page=3, # Page 3
site=None,
time_period=None,
ctx=mock_context
)
# Verify that pagination was correctly applied
assert mock_search_params is not None
assert mock_search_params["offset"] == 10 # (page-1) * count
@pytest.mark.asyncio
async def test_duckduckgo_web_search_error_handling(mock_context):
"""Test error handling in the duckduckgo_web_search tool."""
# Mock the search function to raise an exception
async def mock_error(*args, **kwargs):
raise ValueError("Test error")
# Mock the error reporting method
mock_context.error = AsyncMock()
with patch('mcp_duckduckgo.tools.duckduckgo_search', mock_error):
# In the actual implementation, errors are caught and an empty result is returned
# with error logging, not raising the exception
result = await duckduckgo_web_search(
query="test query",
count=5,
page=1,
site=None,
time_period=None,
ctx=mock_context
)
# Verify error was reported
assert mock_context.error.called
# Check that we got a fallback empty result
assert isinstance(result, SearchResponse)
assert len(result.results) == 0
@pytest.mark.asyncio
async def test_duckduckgo_get_details(mock_context):
"""Test the duckduckgo_get_details tool."""
# The implementation doesn't actually make HTTP requests, it just creates a
# placeholder DetailedResult with the domain extracted from the URL
# Call the tool
url = "https://example.com/page"
result = await duckduckgo_get_details(
url=url,
ctx=mock_context
)
# Verify the result
assert isinstance(result, DetailedResult)
assert result.url == url
assert result.domain == "example.com"
assert result.content_snippet == "Content not available" # Default placeholder
@pytest.mark.asyncio
async def test_duckduckgo_related_searches(mock_context):
"""Test the duckduckgo_related_searches tool."""
# The implementation doesn't make HTTP requests, it generates placeholder related searches
# Call the tool
query = "test query"
count = 5
result = await duckduckgo_related_searches(
query=query,
count=count,
ctx=mock_context
)
# Verify the result
assert isinstance(result, list)
assert len(result) == count
# Check that the related searches are based on the query
for related_search in result:
assert query in related_search.lower()
@pytest.mark.asyncio
async def test_duckduckgo_related_searches_count(mock_context):
"""Test the duckduckgo_related_searches tool with different counts."""
# Test with different counts
for count in [1, 3, 7, 10]:
result = await duckduckgo_related_searches(
query="test query",
count=count,
ctx=mock_context
)
# Verify the count
assert len(result) == count