test_server_tools.py•22 kB
"""
Comprehensive tests for Wikipedia MCP server tools.
"""
import asyncio
import pytest
import requests
from unittest.mock import Mock, patch, MagicMock
from wikipedia_mcp.server import create_server
from wikipedia_mcp.wikipedia_client import WikipediaClient
class TestWikipediaClient:
"""Test the WikipediaClient class."""
def setup_method(self):
"""Set up test fixtures."""
self.client = WikipediaClient()
@patch("wikipedia_mcp.wikipedia_client.requests.get")
def test_search_success(self, mock_get):
"""Test successful search operation."""
# Mock API response
mock_response = Mock()
mock_response.raise_for_status.return_value = None
mock_response.json.return_value = {
"query": {
"search": [
{
"title": "Python (programming language)",
"snippet": "Python is a programming language",
"pageid": 12345,
"wordcount": 1000,
"timestamp": "2023-01-01T00:00:00Z",
}
]
}
}
mock_get.return_value = mock_response
# Test search
results = self.client.search("Python", limit=1)
# Assertions
assert len(results) == 1
assert results[0]["title"] == "Python (programming language)"
assert results[0]["snippet"] == "Python is a programming language"
assert results[0]["pageid"] == 12345
mock_get.assert_called_once()
@patch("wikipedia_mcp.wikipedia_client.requests.get")
def test_search_failure(self, mock_get):
"""Test handling of search failures when exceptions occur."""
mock_get.side_effect = Exception("API error")
results = self.client.search("Python")
assert results == []
@patch("wikipedia_mcp.wikipedia_client.requests.get")
def test_search_empty_query(self, mock_get):
"""Empty queries should not trigger HTTP calls."""
results = self.client.search("")
assert results == []
mock_get.assert_not_called()
results = self.client.search(" ")
assert results == []
mock_get.assert_not_called()
@patch("wikipedia_mcp.wikipedia_client.requests.get")
def test_search_limit_validation(self, mock_get):
"""Ensure the limit is bounded within API constraints."""
mock_response = Mock()
mock_response.raise_for_status.return_value = None
mock_response.json.return_value = {"query": {"search": []}}
mock_get.return_value = mock_response
self.client.search("Python", limit=1000)
_, kwargs = mock_get.call_args
assert kwargs["params"]["srlimit"] == 500
@patch("wikipedia_mcp.wikipedia_client.requests.get")
def test_search_api_error(self, mock_get):
"""Handle API error payloads gracefully."""
mock_response = Mock()
mock_response.raise_for_status.return_value = None
mock_response.json.return_value = {"error": {"code": "badquery", "info": "Invalid query"}}
mock_get.return_value = mock_response
results = self.client.search("Python")
assert results == []
@patch("wikipedia_mcp.wikipedia_client.WikipediaClient._extract_sections")
def test_get_article_success(self, mock_extract_sections):
"""Test successful article retrieval."""
# Mock Wikipedia page
mock_page = Mock()
mock_page.exists.return_value = True
mock_page.title = "Python (programming language)"
mock_page.pageid = 12345
mock_page.summary = "Python is a programming language"
mock_page.text = "Full article text here"
mock_page.fullurl = "https://en.wikipedia.org/wiki/Python_(programming_language)"
mock_page.categories = {"Category:Programming languages": None}
mock_page.links = {"Link1": None, "Link2": None}
mock_page.sections = []
mock_extract_sections.return_value = [{"title": "History", "level": 0, "text": "History text", "sections": []}]
with patch.object(self.client.wiki, "page", return_value=mock_page):
article = self.client.get_article("Python (programming language)")
assert article["exists"] is True
assert article["title"] == "Python (programming language)"
assert article["pageid"] == 12345
assert article["summary"] == "Python is a programming language"
assert article["text"] == "Full article text here"
assert len(article["categories"]) == 1
assert len(article["links"]) == 2
def test_get_article_not_found(self):
"""Test article retrieval for non-existent page."""
mock_page = Mock()
mock_page.exists.return_value = False
with patch.object(self.client.wiki, "page", return_value=mock_page):
article = self.client.get_article("NonExistentPage")
assert article["exists"] is False
assert article["error"] == "Page does not exist"
def test_get_summary_success(self):
"""Test successful summary retrieval."""
mock_page = Mock()
mock_page.exists.return_value = True
mock_page.summary = "This is a test summary"
with patch.object(self.client.wiki, "page", return_value=mock_page):
summary = self.client.get_summary("Test Page")
assert summary == "This is a test summary"
def test_get_summary_not_found(self):
"""Test summary retrieval for non-existent page."""
mock_page = Mock()
mock_page.exists.return_value = False
with patch.object(self.client.wiki, "page", return_value=mock_page):
summary = self.client.get_summary("NonExistentPage")
assert "No Wikipedia article found for 'NonExistentPage'" in summary
@patch("wikipedia_mcp.wikipedia_client.WikipediaClient._extract_sections")
def test_get_sections_success(self, mock_extract_sections):
"""Test successful sections retrieval."""
mock_page = Mock()
mock_page.exists.return_value = True
mock_page.sections = []
mock_extract_sections.return_value = [
{"title": "History", "level": 0, "text": "History text", "sections": []},
{"title": "Features", "level": 0, "text": "Features text", "sections": []},
]
with patch.object(self.client.wiki, "page", return_value=mock_page):
sections = self.client.get_sections("Test Page")
assert len(sections) == 2
assert sections[0]["title"] == "History"
assert sections[1]["title"] == "Features"
def test_get_sections_not_found(self):
"""Test sections retrieval for non-existent page."""
mock_page = Mock()
mock_page.exists.return_value = False
with patch.object(self.client.wiki, "page", return_value=mock_page):
sections = self.client.get_sections("NonExistentPage")
assert sections == []
def test_get_links_success(self):
"""Test successful links retrieval."""
mock_page = Mock()
mock_page.exists.return_value = True
mock_page.links = {"Link1": None, "Link2": None, "Link3": None}
with patch.object(self.client.wiki, "page", return_value=mock_page):
links = self.client.get_links("Test Page")
assert len(links) == 3
assert "Link1" in links
assert "Link2" in links
assert "Link3" in links
def test_get_links_not_found(self):
"""Test links retrieval for non-existent page."""
mock_page = Mock()
mock_page.exists.return_value = False
with patch.object(self.client.wiki, "page", return_value=mock_page):
links = self.client.get_links("NonExistentPage")
assert links == []
def test_get_related_topics_success(self):
"""Test successful related topics retrieval."""
mock_page = Mock()
mock_page.exists.return_value = True
mock_page.links = {"Related Link 1": None, "Related Link 2": None}
mock_page.categories = {"Category:Test Category": None}
# Mock related page
mock_related_page = Mock()
mock_related_page.exists.return_value = True
mock_related_page.summary = "Summary of related page"
mock_related_page.fullurl = "https://en.wikipedia.org/wiki/Related_Link_1"
with patch.object(self.client.wiki, "page") as mock_wiki_page:
mock_wiki_page.side_effect = lambda title: (
mock_related_page if title in ["Related Link 1", "Related Link 2"] else mock_page
)
related = self.client.get_related_topics("Test Page", limit=3)
assert len(related) >= 1
assert any(topic["type"] == "link" for topic in related)
def test_get_related_topics_not_found(self):
"""Test related topics retrieval for non-existent page."""
mock_page = Mock()
mock_page.exists.return_value = False
with patch.object(self.client.wiki, "page", return_value=mock_page):
related = self.client.get_related_topics("NonExistentPage")
assert related == []
def test_extract_sections(self):
"""Test section extraction helper method."""
# Mock section structure
mock_subsection = Mock()
mock_subsection.title = "Subsection"
mock_subsection.text = "Subsection text"
mock_subsection.sections = []
mock_section = Mock()
mock_section.title = "Main Section"
mock_section.text = "Main section text"
mock_section.sections = [mock_subsection]
sections = self.client._extract_sections([mock_section])
assert len(sections) == 1
assert sections[0]["title"] == "Main Section"
assert sections[0]["level"] == 0
assert sections[0]["text"] == "Main section text"
assert len(sections[0]["sections"]) == 1
assert sections[0]["sections"][0]["title"] == "Subsection"
assert sections[0]["sections"][0]["level"] == 1
def test_summarize_for_query_success(self):
"""Test successful query-focused summary retrieval."""
mock_page = Mock()
mock_page.exists.return_value = True
mock_page.title = "Test Page"
mock_page.text = (
"This is a long text about a specific keyword. " "We want to find this keyword and summarize around it."
)
mock_page.summary = "This is a general summary."
with patch.object(self.client.wiki, "page", return_value=mock_page):
summary = self.client.summarize_for_query("Test Page", "keyword", max_length=50)
assert "keyword" in summary
assert len(summary) <= 50 + 3 # for "..."
def test_summarize_for_query_not_found(self):
"""Test query-focused summary when query is not in text."""
mock_page = Mock()
mock_page.exists.return_value = True
mock_page.title = "Test Page"
mock_page.text = "This is some other text."
mock_page.summary = "A general summary content."
with patch.object(self.client.wiki, "page", return_value=mock_page):
summary = self.client.summarize_for_query("Test Page", "missing_keyword", max_length=30)
assert "A general summary content."[:30] in summary # Should return start of summary
def test_summarize_for_query_page_not_exists(self):
"""Test query-focused summary when page does not exist."""
mock_page = Mock()
mock_page.exists.return_value = False
with patch.object(self.client.wiki, "page", return_value=mock_page):
summary = self.client.summarize_for_query("NonExistent Page", "keyword")
assert "No Wikipedia article found for 'NonExistent Page'" in summary
def test_summarize_section_success(self):
"""Test successful section summary retrieval."""
mock_section_target = Mock()
mock_section_target.title = "Target Section"
mock_section_target.text = "This is the text of the target section. It is fairly long."
mock_section_target.sections = []
mock_page = Mock()
mock_page.exists.return_value = True
mock_page.title = "Test Page"
mock_page.sections = [mock_section_target]
with patch.object(self.client.wiki, "page", return_value=mock_page):
summary = self.client.summarize_section("Test Page", "Target Section", max_length=20)
assert summary == "This is the text of ..."
assert len(summary) <= 20 + 3
def test_summarize_section_not_found(self):
"""Test section summary when section does not exist."""
mock_other_section = Mock()
mock_other_section.title = "Other Section"
mock_other_section.text = "Some text."
mock_other_section.sections = []
mock_page = Mock()
mock_page.exists.return_value = True
mock_page.title = "Test Page"
mock_page.sections = [mock_other_section]
with patch.object(self.client.wiki, "page", return_value=mock_page):
summary = self.client.summarize_section("Test Page", "Missing Section")
assert "Section 'Missing Section' not found or is empty" in summary
def test_summarize_section_page_not_exists(self):
"""Test section summary when page does not exist."""
mock_page = Mock()
mock_page.exists.return_value = False
with patch.object(self.client.wiki, "page", return_value=mock_page):
summary = self.client.summarize_section("NonExistent Page", "Any Section")
assert "No Wikipedia article found for 'NonExistent Page'" in summary
def test_extract_facts_success_from_summary(self):
"""Test successful fact extraction from page summary."""
mock_page = Mock()
mock_page.exists.return_value = True
mock_page.title = "Test Page"
mock_page.summary = "Fact one. Fact two. Fact three. Fact four. Fact five. Fact six."
mock_page.sections = []
with patch.object(self.client.wiki, "page", return_value=mock_page):
facts = self.client.extract_facts("Test Page", count=3)
assert len(facts) == 3
assert facts[0] == "Fact one."
assert facts[1] == "Fact two."
assert facts[2] == "Fact three."
def test_extract_facts_success_from_section(self):
"""Test successful fact extraction from a specific section."""
mock_target_section = Mock()
mock_target_section.title = "Key Info"
mock_target_section.text = "Important fact A. Important fact B. Important fact C."
mock_target_section.sections = []
mock_page = Mock()
mock_page.exists.return_value = True
mock_page.title = "Test Page"
mock_page.summary = "General summary."
mock_page.sections = [mock_target_section]
with patch.object(self.client.wiki, "page", return_value=mock_page):
facts = self.client.extract_facts("Test Page", topic_within_article="Key Info", count=2)
assert len(facts) == 2
assert facts[0] == "Important fact A."
assert facts[1] == "Important fact B."
def test_extract_facts_section_not_found_fallback_to_summary(self):
"""Test fact extraction falls back to summary if section not found."""
mock_page = Mock()
mock_page.exists.return_value = True
mock_page.title = "Test Page"
mock_page.summary = "Summary fact 1. Summary fact 2."
mock_page.sections = [] # No sections defined
with patch.object(self.client.wiki, "page", return_value=mock_page):
facts = self.client.extract_facts("Test Page", topic_within_article="Missing Section", count=1)
assert len(facts) == 1
assert facts[0] == "Summary fact 1."
def test_extract_facts_page_not_exists(self):
"""Test fact extraction when page does not exist."""
mock_page = Mock()
mock_page.exists.return_value = False
with patch.object(self.client.wiki, "page", return_value=mock_page):
facts = self.client.extract_facts("NonExistent Page")
assert len(facts) == 1
assert "No Wikipedia article found for 'NonExistent Page'" in facts[0]
def test_extract_facts_no_content(self):
"""Test fact extraction when there is no summary or section text."""
mock_page = Mock()
mock_page.exists.return_value = True
mock_page.title = "Empty Page"
mock_page.summary = "" # Empty summary
mock_page.sections = []
with patch.object(self.client.wiki, "page", return_value=mock_page):
facts = self.client.extract_facts("Empty Page")
assert len(facts) == 1
assert "No content found to extract facts from." in facts[0]
class TestMCPServerTools:
"""Test the MCP server tools."""
def setup_method(self):
"""Set up test fixtures."""
self.server = create_server()
@patch("wikipedia_mcp.server.WikipediaClient")
def test_create_server_with_language(self, MockWikipediaClient):
"""Test that create_server initializes WikipediaClient with the specified language."""
create_server(language="ja")
MockWikipediaClient.assert_called_once_with(language="ja", country=None, enable_cache=False, access_token=None)
@patch("wikipedia_mcp.server.WikipediaClient")
def test_create_server_default_language(self, MockWikipediaClient):
"""Test that create_server uses 'en' if no language is specified."""
create_server()
MockWikipediaClient.assert_called_once_with(language="en", country=None, enable_cache=False, access_token=None)
@patch("wikipedia_mcp.server.WikipediaClient")
def test_create_server_with_cache_enabled(self, MockWikipediaClient):
"""Test that create_server initializes WikipediaClient with caching enabled."""
create_server(language="en", enable_cache=True)
MockWikipediaClient.assert_called_once_with(language="en", country=None, enable_cache=True, access_token=None)
@patch("wikipedia_mcp.server.WikipediaClient")
def test_create_server_with_cache_disabled(self, MockWikipediaClient):
"""Test that create_server initializes WikipediaClient with caching disabled by default."""
create_server(language="en", enable_cache=False)
MockWikipediaClient.assert_called_once_with(language="en", country=None, enable_cache=False, access_token=None)
def test_search_wikipedia_tool(self):
"""Test search_wikipedia tool registration."""
# Test that server can be created without errors
server = create_server()
assert server is not None
assert server.name == "Wikipedia"
def test_get_article_tool(self):
"""Test get_article tool registration."""
server = create_server()
assert server is not None
assert server.name == "Wikipedia"
def test_get_summary_tool(self):
"""Test get_summary tool registration."""
server = create_server()
assert server is not None
assert server.name == "Wikipedia"
def test_get_sections_tool(self):
"""Test get_sections tool registration."""
server = create_server()
assert server is not None
assert server.name == "Wikipedia"
def test_get_links_tool(self):
"""Test get_links tool registration."""
server = create_server()
assert server is not None
assert server.name == "Wikipedia"
def test_get_related_topics_tool(self):
"""Test get_related_topics tool registration."""
server = create_server()
assert server is not None
assert server.name == "Wikipedia"
def test_connectivity_tool(self):
"""Ensure the connectivity test tool is registered."""
server = create_server()
assert server is not None
async def gather_tools():
tools = await server.get_tools()
return list(tools.keys())
tool_names = asyncio.run(gather_tools())
assert "test_wikipedia_connectivity" in tool_names
class TestIntegration:
"""Integration tests for the complete system."""
def test_server_creation(self):
"""Test that server can be created without errors."""
server = create_server()
assert server is not None
assert server.name == "Wikipedia"
def test_client_initialization(self):
"""Test that client can be initialized with different languages."""
client_en = WikipediaClient("en")
assert client_en.original_language == "en"
assert client_en.base_language == "en"
assert client_en.language_variant is None
client_es = WikipediaClient("es")
assert client_es.original_language == "es"
assert client_es.base_language == "es"
assert client_es.language_variant is None
@pytest.mark.integration
def test_real_wikipedia_search(self):
"""Integration test with real Wikipedia API (marked as integration)."""
client = WikipediaClient()
results = client.search("Python programming", limit=1)
# This test requires internet connection
if results: # Only assert if we got results (network dependent)
assert len(results) >= 1
assert "title" in results[0]
assert "snippet" in results[0]
@pytest.mark.integration
def test_real_wikipedia_summary(self):
"""Integration test for real summary retrieval."""
client = WikipediaClient()
summary = client.get_summary("Python (programming language)")
# This test requires internet connection
if "Error" not in summary and "No Wikipedia article found" not in summary:
assert len(summary) > 0
assert isinstance(summary, str)
if __name__ == "__main__":
pytest.main([__file__, "-v"])