MCP DuckDuckGo Search Plugin

  • tests
""" Shared test fixtures and configurations for MCP DuckDuckGo plugin tests. """ from typing import Any, Dict, List, Callable from unittest.mock import AsyncMock, MagicMock import httpx import pytest from bs4 import BeautifulSoup from mcp.server.fastmcp import Context # Sample HTML response for mocking DuckDuckGo search results SAMPLE_HTML = """ <html> <body> <table> <tr class="result-link"> <td> <a href="https://example.com/page1">Example Page 1</a> </td> </tr> <tr class="result-snippet"> <td>This is a description for Example Page 1</td> </tr> <tr class="result-link"> <td> <a href="https://example.com/page2">Example Page 2</a> </td> </tr> <tr class="result-snippet"> <td>This is a description for Example Page 2</td> </tr> </table> </body> </html> """ # Sample search results for testing SAMPLE_SEARCH_RESULTS = [ { "title": "Example Page 1", "url": "https://example.com/page1", "description": "This is a description for Example Page 1", "published_date": None, "domain": "example.com" }, { "title": "Example Page 2", "url": "https://example.com/page2", "description": "This is a description for Example Page 2", "published_date": None, "domain": "example.com" } ] class MockResponse: """Mock for httpx.Response""" def __init__(self, text: str, status_code: int = 200): self.text = text self.status_code = status_code def raise_for_status(self) -> None: """Mock the raise_for_status method""" if self.status_code >= 400: raise httpx.HTTPStatusError( message=f"HTTP Error {self.status_code}", request=httpx.Request("POST", "https://lite.duckduckgo.com/lite/"), response=self ) class MockContext(MagicMock): """Mock for MCP Context""" def __init__(self, *args: Any, **kwargs: Any) -> None: super().__init__(*args, **kwargs) self.lifespan_context = {"http_client": AsyncMock()} async def report_progress(self, current: int, total: int) -> None: """Mock for report_progress method""" pass async def error(self, message: str) -> None: """Mock for error method""" pass async def info(self, message: str) -> None: """Mock for info method""" pass @pytest.fixture def mock_context() -> MockContext: """Return a mock Context object""" return MockContext() @pytest.fixture def mock_http_client() -> AsyncMock: """Return a mock AsyncClient""" client = AsyncMock() client.post = AsyncMock(return_value=MockResponse(SAMPLE_HTML)) client.get = AsyncMock(return_value=MockResponse(SAMPLE_HTML)) client.aclose = AsyncMock() return client @pytest.fixture def sample_search_params() -> Dict[str, Any]: """Return sample search parameters""" return { "query": "test query", "count": 2, "page": 1, "site": None, "time_period": None } @pytest.fixture def sample_search_results() -> List[Dict[str, Any]]: """Return sample search results""" return SAMPLE_SEARCH_RESULTS @pytest.fixture def sample_soup() -> BeautifulSoup: """Return a BeautifulSoup object with sample HTML""" return BeautifulSoup(SAMPLE_HTML, "html.parser") @pytest.fixture def mock_search_function() -> Callable[[Dict[str, Any], Context], Dict[str, Any]]: """Mock for the duckduckgo_search function""" async def mock_search(params: Dict[str, Any], ctx: Context) -> Dict[str, Any]: return { "results": SAMPLE_SEARCH_RESULTS, "total_results": len(SAMPLE_SEARCH_RESULTS) } return mock_search