Skip to main content
Glama
chrismannina

PubMed MCP Server

by chrismannina
test_tool_handler_extended.py18.1 kB
""" Extended test cases for tool handler functionality. """ from datetime import datetime from unittest.mock import AsyncMock, Mock, patch import pytest from src.models import Article, Author, Journal, MeSHTerm, SearchResult from src.tool_handler import ToolHandler class TestToolHandlerExtended: """Extended tests for tool handler to improve coverage.""" @pytest.fixture def mock_pubmed_client(self): """Create a mock PubMed client.""" client = Mock() client.search_articles = AsyncMock() client.get_article_details = AsyncMock() client.search_by_author = AsyncMock() client.find_related_articles = AsyncMock() return client @pytest.fixture def mock_cache(self): """Create a mock cache manager.""" return Mock() @pytest.fixture def tool_handler(self, mock_pubmed_client, mock_cache): """Create a tool handler with mocked dependencies.""" return ToolHandler(mock_pubmed_client, mock_cache) @pytest.fixture def sample_article(self): """Create a sample article for testing.""" return Article( pmid="12345678", title="Sample Article Title", abstract="Sample abstract content.", authors=[ Author( last_name="Smith", first_name="John", initials="J", affiliation="University Hospital", ) ], journal=Journal(title="Sample Journal", volume="10", issue="1"), pub_date="2023/01/01", doi="10.1000/example", keywords=["keyword1", "keyword2", "keyword3"], mesh_terms=[MeSHTerm(descriptor_name="Sample Term", major_topic=True)], article_types=["Journal Article"], ) @pytest.mark.asyncio async def test_handle_export_citations(self, tool_handler, mock_pubmed_client, sample_article): """Test export citations functionality.""" mock_pubmed_client.get_article_details.return_value = [sample_article] result = await tool_handler._handle_export_citations( {"pmids": ["12345678"], "format": "bibtex", "include_abstracts": False} ) assert not result.is_error assert len(result.content) == 1 content_text = result.content[0]["text"] assert "Citations in BIBTEX format" in content_text @pytest.mark.asyncio async def test_handle_export_citations_no_pmids(self, tool_handler): """Test export citations with no PMIDs.""" result = await tool_handler._handle_export_citations({}) assert result.is_error assert "PMIDs parameter is required" in result.content[0]["text"] @pytest.mark.asyncio async def test_handle_export_citations_no_articles_found( self, tool_handler, mock_pubmed_client ): """Test export citations when no articles are found.""" mock_pubmed_client.get_article_details.return_value = [] result = await tool_handler._handle_export_citations( {"pmids": ["99999999"], "format": "apa"} ) assert result.is_error assert "No articles found" in result.content[0]["text"] @pytest.mark.asyncio async def test_handle_search_mesh_terms(self, tool_handler, mock_pubmed_client, sample_article): """Test MeSH term search.""" search_result = SearchResult( query="cancer[MeSH Terms]", total_results=100, returned_results=1, articles=[sample_article], search_time=0.5, ) mock_pubmed_client.search_articles.return_value = search_result result = await tool_handler._handle_search_mesh_terms({"term": "cancer", "max_results": 20}) assert not result.is_error assert "Articles with MeSH term: cancer" in result.content[0]["text"] assert "Total Results: 100" in result.content[0]["text"] @pytest.mark.asyncio async def test_handle_search_mesh_terms_no_term(self, tool_handler): """Test MeSH term search without term.""" result = await tool_handler._handle_search_mesh_terms({}) assert result.is_error assert "MeSH term is required" in result.content[0]["text"] @pytest.mark.asyncio async def test_handle_search_by_journal(self, tool_handler, mock_pubmed_client, sample_article): """Test journal-based search.""" search_result = SearchResult( query="Nature[Journal]", total_results=500, returned_results=1, articles=[sample_article], search_time=0.3, ) mock_pubmed_client.search_articles.return_value = search_result result = await tool_handler._handle_search_by_journal( { "journal_name": "Nature", "max_results": 20, "date_from": "2023/01/01", "date_to": "2023/12/31", } ) assert not result.is_error assert "Recent Articles from Nature" in result.content[0]["text"] assert "Total Results: 500" in result.content[0]["text"] @pytest.mark.asyncio async def test_handle_search_by_journal_no_name(self, tool_handler): """Test journal search without journal name.""" result = await tool_handler._handle_search_by_journal({}) assert result.is_error assert "Journal name is required" in result.content[0]["text"] @pytest.mark.asyncio async def test_handle_get_trending_topics(self, tool_handler, mock_pubmed_client): """Test trending topics analysis.""" # Create articles with keywords for testing article_with_keywords = Article( pmid="12345678", title="Trending Research", journal=Journal(title="Test Journal"), keywords=["AI", "machine learning", "healthcare"], ) search_result = SearchResult( query="trending AND medicine", total_results=50, returned_results=1, articles=[article_with_keywords], search_time=0.4, ) mock_pubmed_client.search_articles.return_value = search_result result = await tool_handler._handle_get_trending_topics({"category": "AI", "days": 7}) assert not result.is_error assert "Trending Topics in AI" in result.content[0]["text"] @pytest.mark.asyncio async def test_handle_get_trending_topics_no_category(self, tool_handler, mock_pubmed_client): """Test trending topics without specific category.""" search_result = SearchResult( query="trending AND medicine", total_results=30, returned_results=0, articles=[], search_time=0.2, ) mock_pubmed_client.search_articles.return_value = search_result result = await tool_handler._handle_get_trending_topics({"days": 14}) assert not result.is_error assert "Trending Topics in Medicine" in result.content[0]["text"] @pytest.mark.asyncio async def test_handle_analyze_research_trends(self, tool_handler, mock_pubmed_client): """Test research trends analysis.""" search_result = SearchResult( query="cancer treatment", total_results=1000, returned_results=5, articles=[], search_time=0.6, ) mock_pubmed_client.search_articles.return_value = search_result result = await tool_handler._handle_analyze_research_trends( {"topic": "cancer treatment", "years_back": 3, "include_subtopics": True} ) assert not result.is_error assert "Research Trends for: cancer treatment" in result.content[0]["text"] current_year = datetime.now().year assert f"Analysis Period: {current_year - 3} - {current_year}" in result.content[0]["text"] @pytest.mark.asyncio async def test_handle_analyze_research_trends_no_topic(self, tool_handler): """Test research trends analysis without topic.""" result = await tool_handler._handle_analyze_research_trends({}) assert result.is_error assert "Topic is required" in result.content[0]["text"] @pytest.mark.asyncio async def test_handle_compare_articles(self, tool_handler, mock_pubmed_client, sample_article): """Test article comparison.""" mock_pubmed_client.get_article_details.return_value = [sample_article, sample_article] result = await tool_handler._handle_compare_articles( { "pmids": ["12345678", "87654321"], "comparison_fields": ["authors", "mesh_terms", "abstracts"], } ) assert not result.is_error assert "Comparison of 2 Articles" in result.content[0]["text"] @pytest.mark.asyncio async def test_handle_compare_articles_insufficient_pmids(self, tool_handler): """Test article comparison with insufficient PMIDs.""" result = await tool_handler._handle_compare_articles({"pmids": ["12345678"]}) assert result.is_error assert "Please provide 2-5 PMIDs for comparison" in result.content[0]["text"] @pytest.mark.asyncio async def test_handle_compare_articles_too_many_pmids(self, tool_handler): """Test article comparison with too many PMIDs.""" pmids = [str(i) for i in range(12345678, 12345684)] # 6 PMIDs result = await tool_handler._handle_compare_articles({"pmids": pmids}) assert result.is_error assert "Please provide 2-5 PMIDs for comparison" in result.content[0]["text"] @pytest.mark.asyncio async def test_handle_compare_articles_insufficient_articles( self, tool_handler, mock_pubmed_client ): """Test article comparison when insufficient articles found.""" mock_pubmed_client.get_article_details.return_value = [ Article(pmid="12345678", title="Single Article", journal=Journal(title="Test")) ] result = await tool_handler._handle_compare_articles( {"pmids": ["12345678", "87654321", "11111111"]} ) assert result.is_error assert "Not enough valid articles found for comparison" in result.content[0]["text"] @pytest.mark.asyncio async def test_handle_get_journal_metrics(self, tool_handler, mock_pubmed_client): """Test journal metrics functionality.""" articles_with_types = [ Article( pmid="12345678", title="Article 1", journal=Journal(title="Test Journal"), article_types=["Journal Article", "Research Support"], ), Article( pmid="87654321", title="Article 2", journal=Journal(title="Test Journal"), article_types=["Review"], ), ] search_result = SearchResult( query="Test Journal[Journal]", total_results=200, returned_results=2, articles=articles_with_types, search_time=0.3, ) mock_pubmed_client.search_articles.return_value = search_result result = await tool_handler._handle_get_journal_metrics( {"journal_name": "Test Journal", "include_recent_articles": True} ) assert not result.is_error assert "Journal Metrics: Test Journal" in result.content[0]["text"] current_year = datetime.now().year assert f"Articles in {current_year}: 200" in result.content[0]["text"] @pytest.mark.asyncio async def test_handle_get_journal_metrics_no_name(self, tool_handler): """Test journal metrics without journal name.""" result = await tool_handler._handle_get_journal_metrics({}) assert result.is_error assert "Journal name is required" in result.content[0]["text"] @pytest.mark.asyncio async def test_handle_advanced_search(self, tool_handler, mock_pubmed_client, sample_article): """Test advanced search functionality.""" search_result = SearchResult( query="(cancer[Title]) AND (treatment[Abstract])", total_results=150, returned_results=1, articles=[sample_article], search_time=0.8, ) mock_pubmed_client.search_articles.return_value = search_result result = await tool_handler._handle_advanced_search( { "search_terms": [ {"term": "cancer", "field": "title"}, {"term": "treatment", "field": "abstract", "operator": "AND"}, ], "max_results": 50, "filters": { "publication_types": ["Journal Article"], "languages": ["eng"], "species": ["humans"], }, } ) assert not result.is_error assert "Advanced Search Results" in result.content[0]["text"] assert "Total Results: 150" in result.content[0]["text"] @pytest.mark.asyncio async def test_handle_advanced_search_no_terms(self, tool_handler): """Test advanced search without search terms.""" result = await tool_handler._handle_advanced_search({}) assert result.is_error assert "Search terms are required" in result.content[0]["text"] def test_format_article_summary_with_dict(self, tool_handler): """Test article summary formatting with dictionary input.""" article_dict = { "title": "Test Article", "pmid": "12345678", "authors": [{"first_name": "John", "last_name": "Smith"}], "journal": {"title": "Test Journal"}, "pub_date": "2023/01/01", "abstract": "This is a test abstract.", "mesh_terms": [{"descriptor_name": "Test Term"}], } result = tool_handler._format_article_summary(article_dict, 1) assert "1. Test Article" in result assert "John Smith" in result assert "Test Journal" in result assert "PMID: 12345678" in result def test_format_article_summary_with_object_fallback(self, tool_handler): """Test article summary formatting with object fallback.""" class MockArticle: title = "Mock Article" pmid = "99999999" authors = [] journal = None pub_date = None abstract = "" mesh_terms = [] mock_article = MockArticle() result = tool_handler._format_article_summary(mock_article, 1) assert "1. Mock Article" in result assert "PMID: 99999999" in result def test_format_article_summary_with_highlight_mesh(self, tool_handler): """Test article summary formatting with MeSH term highlighting.""" article_dict = { "title": "Test Article", "pmid": "12345678", "authors": [], "journal": {"title": "Test Journal"}, "pub_date": None, "abstract": "", "mesh_terms": [{"descriptor_name": "Cancer"}], } result = tool_handler._format_article_summary(article_dict, 1, highlight_mesh="cancer") assert "**Other: Cancer**" in result # Should be highlighted with the "Other:" prefix def test_format_article_details(self, tool_handler, sample_article): """Test detailed article formatting.""" result = tool_handler._format_article_details(sample_article, 1) assert "1. Sample Article Title" in result assert "**Authors:**" in result assert "1. John Smith (University Hospital)" in result assert "**Journal:** Sample Journal" in result assert "**Publication Date:**" in result assert "**Abstract:**" in result assert "**Keywords:**" in result assert "**MeSH Terms:**" in result assert "**Article Types:**" in result assert "**Links:**" in result assert "https://doi.org/10.1000/example" in result def test_format_article_details_many_authors(self, tool_handler): """Test article details formatting with many authors.""" authors = [ Author( last_name=f"Author{i}", first_name=f"First{i}", initials=f"F{i}", affiliation=f"Institution {i}" if i < 3 else None, ) for i in range(8) ] article = Article( pmid="12345678", title="Many Authors Article", authors=authors, journal=Journal(title="Test Journal"), ) result = tool_handler._format_article_details(article, 1) assert "... and 3 more authors" in result # Should show truncation message @pytest.mark.asyncio async def test_handle_tool_call_exception(self, tool_handler): """Test tool call exception handling.""" # Mock a method to raise an exception with patch.object( tool_handler, "_handle_search_pubmed", side_effect=Exception("Test error") ): result = await tool_handler.handle_tool_call("search_pubmed", {"query": "test"}) assert result.is_error assert "Error executing search_pubmed: Test error" in result.content[0]["text"] @pytest.mark.asyncio async def test_handle_tool_call_none_arguments(self, tool_handler): """Test tool call with None arguments.""" result = await tool_handler.handle_tool_call("search_pubmed", None) assert result.is_error assert "Arguments cannot be None" in result.content[0]["text"] @pytest.mark.asyncio async def test_handle_unknown_tool(self, tool_handler): """Test handling of unknown tool.""" result = await tool_handler.handle_tool_call("unknown_tool", {}) assert result.is_error assert "Unknown tool: unknown_tool" in result.content[0]["text"]

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/chrismannina/pubmed-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server