Skip to main content
Glama
gqy20

Europe PMC Literature Search MCP Server

test_tool_core.py23.5 kB
#!/usr/bin/env python3 """ 工具核心逻辑单元测试 测试6工具架构的核心业务逻辑 """ import sys from pathlib import Path from unittest.mock import Mock import pytest # 添加src目录到Python路径 project_root = Path(__file__).parent.parent src_path = project_root / "src" if str(src_path) not in sys.path: sys.path.insert(0, str(src_path)) class TestSearchToolsCore: """测试搜索工具核心逻辑""" @pytest.mark.unit def test_identifier_type_extraction(self): """测试标识符类型提取逻辑""" from article_mcp.tools.core.search_tools import _extract_identifier_type test_cases = [ ("10.1234/test.doi", "doi"), ("https://doi.org/10.1234/test", "doi"), ("12345678", "pmid"), ("PMID:12345678", "pmid"), ("PMC123456", "pmcid"), ("PMCID:PMC123456", "pmcid"), ("arXiv:2301.00001", "arxiv_id"), ("unknown_format", "doi"), # 默认为doi ] for identifier, expected_type in test_cases: result = _extract_identifier_type(identifier) assert ( result == expected_type ), f"Failed for {identifier}: expected {expected_type}, got {result}" @pytest.mark.unit def test_search_results_merging(self): """测试搜索结果合并逻辑""" from article_mcp.tools.core.search_tools import _merge_and_deduplicate_results # 模拟多数据源结果 results_by_source = { "europe_pmc": [ { "title": "Machine Learning in Healthcare", "authors": ["AI Researcher"], "doi": "10.1234/ml.health.2023", "journal": "Health AI Journal", "publication_date": "2023-06-15", } ], "pubmed": [ { "title": "Machine Learning in Healthcare", "authors": ["AI Researcher", "ML Expert"], "doi": "10.1234/ml.health.2023", # 重复DOI "journal": "Health AI Journal", "publication_date": "2023-06-15", }, { "title": "Deep Learning Applications", "authors": ["DL Specialist"], "doi": "10.5678/dl.apps.2023", "journal": "Machine Learning Today", "publication_date": "2023-05-20", }, ], } logger = Mock() merged_results = _merge_and_deduplicate_results(results_by_source, True, logger) # 验证去重效果 assert len(merged_results) == 2 # 应该有2篇唯一文章 # 验证数据合并 ml_article = next((r for r in merged_results if r["doi"] == "10.1234/ml.health.2023"), None) assert ml_article is not None assert len(ml_article["authors"]) >= 1 # 应该包含作者信息 @pytest.mark.unit def test_search_source_priority(self): """测试搜索数据源优先级""" from article_mcp.tools.core.search_tools import _merge_and_deduplicate_results # 模拟相同文章来自不同数据源 results_by_source = { "crossref": [ { "title": "Important Article", "doi": "10.9999/important.2023", "journal": "High Impact Journal", "source": "crossref", } ], "europe_pmc": [ { "title": "Important Article", "doi": "10.9999/important.2023", "journal": "High Impact Journal", "abstract": "This is the abstract", "source": "europe_pmc", } ], } logger = Mock() merged_results = _merge_and_deduplicate_results(results_by_source, True, logger) # 验证优先级处理 assert len(merged_results) == 1 article = merged_results[0] assert article["doi"] == "10.9999/important.2023" # 应该包含更完整的信息(有摘要) class TestArticleToolsCore: """测试文章工具核心逻辑""" @pytest.mark.unit def test_article_data_standardization(self): """测试文章数据标准化逻辑""" from article_mcp.tools.core.article_tools import _standardize_article_data # 测试不同格式的文章数据 raw_articles = [ { "title": "Test Article 1", "authorString": "Author A; Author B; Author C", "journalTitle": "Test Journal", "pubYear": "2023", "doi": "10.1234/test1.2023", }, { "title": "Test Article 2", "authors": [{"name": "Author X"}, {"name": "Author Y"}], "journal": "Another Journal", "publication_date": "2023-06-15", "doi": "10.5678/test2.2023", "abstract": "Test abstract", }, ] standardized = _standardize_article_data(raw_articles) # 验证标准化结果 assert len(standardized) == 2 for article in standardized: assert "title" in article assert "authors" in article assert "journal" in article assert "publication_date" in article assert isinstance(article["authors"], list) @pytest.mark.unit def test_author_name_parsing(self): """测试作者姓名解析逻辑""" from article_mcp.tools.core.article_tools import _parse_author_names test_cases = [ "Author A; Author B; Author C", "Author A, Author B, Author C", "Author A and Author B", "Author A", ] for author_string in test_cases: authors = _parse_author_names(author_string) assert isinstance(authors, list) assert len(authors) > 0 for author in authors: assert isinstance(author, str) assert len(author.strip()) > 0 @pytest.mark.unit def test_quality_metrics_integration(self): """测试质量指标集成逻辑""" from article_mcp.tools.core.article_tools import _add_quality_metrics base_article = { "title": "Quality Article", "authors": ["Quality Researcher"], "doi": "10.9999/quality.2023", "journal": "Nature", } quality_data = { "impact_factor": 42.5, "quartile": "Q1", "jci": 25.8, "citations": 150, } enhanced_article = _add_quality_metrics(base_article, quality_data) # 验证质量指标添加 assert "quality_metrics" in enhanced_article assert enhanced_article["quality_metrics"]["impact_factor"] == 42.5 assert enhanced_article["quality_metrics"]["quartile"] == "Q1" class TestReferenceToolsCore: """测试参考文献工具核心逻辑""" @pytest.mark.unit def test_reference_deduplication(self): """测试参考文献去重逻辑""" from article_mcp.tools.core.reference_tools import _merge_and_deduplicate_references # 模拟重复的参考文献 references_by_source = { "europe_pmc": [ { "title": "Reference 1", "authors": ["Author One"], "doi": "10.1111/ref1.2020", "journal": "Journal One", "publication_date": "2020-01-01", }, { "title": "Reference 2", "authors": ["Author Two"], "pmid": "12345678", "journal": "Journal Two", "publication_date": "2021-06-15", }, ], "crossref": [ { "title": "Reference 1", # 重复标题 "authors": ["Author One", "Co Author"], "doi": "10.1111/ref1.2020", # 相同DOI "journal": "Journal One", "publication_date": "2020-01-01", "abstract": "Abstract from Crossref", } ], } logger = Mock() merged_refs = _merge_and_deduplicate_references(references_by_source, True, logger) # 验证去重效果 assert len(merged_refs) == 2 # 应该有2篇唯一参考文献 # 验证信息合并 ref1 = next((r for r in merged_refs if r["doi"] == "10.1111/ref1.2020"), None) assert ref1 is not None assert len(ref1["authors"]) >= 1 @pytest.mark.unit def test_reference_completeness_check(self): """测试参考文献完整性检查""" from article_mcp.tools.core.reference_tools import _check_reference_completeness test_references = [ { "title": "Complete Reference", "authors": ["Author One"], "doi": "10.1111/complete.2020", "journal": "Complete Journal", "publication_date": "2020-01-01", }, { "title": "Incomplete Reference", "authors": [], # 缺少作者 "doi": "", # 缺少DOI "journal": "", "publication_date": "", }, ] completeness_scores = [_check_reference_completeness(ref) for ref in test_references] # 验证完整性评分 assert completeness_scores[0] > completeness_scores[1] # 完整参考文献得分更高 assert completeness_scores[0] > 0.8 # 完整参考文献应该得分很高 assert completeness_scores[1] < 0.5 # 不完整参考文献得分较低 class TestRelationToolsCore: """测试关系分析工具核心逻辑""" @pytest.mark.unit def test_relation_type_detection(self): """测试关系类型检测逻辑""" from article_mcp.tools.core.relation_tools import _detect_relation_types # 测试关系类型参数 relation_params = [ ["references"], ["similar"], ["citing"], ["references", "similar"], ["references", "similar", "citing"], ] for relations in relation_params: detected_types = _detect_relation_types(relations) assert isinstance(detected_types, list) assert len(detected_types) > 0 for rel_type in detected_types: assert rel_type in ["references", "similar", "citing"] @pytest.mark.unit def test_network_node_creation(self): """测试网络节点创建逻辑""" from article_mcp.tools.core.relation_tools import _create_network_node # 测试不同类型的节点 seed_node = _create_network_node("article1", "Test Article", "seed") reference_node = _create_network_node("ref1", "Reference Article", "reference") citing_node = _create_network_node("cite1", "Citing Article", "citing") # 验证节点结构 for node in [seed_node, reference_node, citing_node]: assert "id" in node assert "label" in node assert "type" in node assert "x" in node assert "y" in node assert seed_node["type"] == "seed" assert reference_node["type"] == "reference" assert citing_node["type"] == "citing" @pytest.mark.unit def test_network_edge_creation(self): """测试网络边创建逻辑""" from article_mcp.tools.core.relation_tools import _create_network_edge # 测试不同类型的边 ref_edge = _create_network_edge(0, 1, "references", 1.0) cite_edge = _create_network_edge(1, 2, "citing", 0.8) similar_edge = _create_network_edge(0, 2, "similar", 0.6) # 验证边结构 for edge in [ref_edge, cite_edge, similar_edge]: assert "source" in edge assert "target" in edge assert "type" in edge assert "weight" in edge assert ref_edge["type"] == "references" assert cite_edge["type"] == "citing" assert similar_edge["type"] == "similar" @pytest.mark.unit def test_network_metrics_calculation(self): """测试网络指标计算逻辑""" from article_mcp.tools.core.relation_tools import _calculate_network_metrics # 模拟网络数据 nodes = [ {"id": "n1", "type": "seed"}, {"id": "n2", "type": "reference"}, {"id": "n3", "type": "citing"}, ] edges = [ {"source": 0, "target": 1, "weight": 1.0}, {"source": 2, "target": 0, "weight": 0.8}, ] metrics = _calculate_network_metrics(nodes, edges) # 验证指标计算 assert "total_nodes" in metrics assert "total_edges" in metrics assert "average_degree" in metrics assert "network_density" in metrics assert metrics["total_nodes"] == 3 assert metrics["total_edges"] == 2 assert metrics["average_degree"] == (2 * 2) / 3 # 2*edges/nodes class TestQualityToolsCore: """测试质量评估工具核心逻辑""" @pytest.mark.unit def test_quality_score_calculation(self): """测试质量评分计算逻辑""" from article_mcp.tools.core.quality_tools import _calculate_quality_score # 测试不同质量指标 quality_metrics = [ {"impact_factor": 42.5, "quartile": "Q1", "jci": 25.8}, {"impact_factor": 2.1, "quartile": "Q3", "jci": 0.8}, {"impact_factor": 0.5, "quartile": "Q4", "jci": 0.1}, ] scores = [_calculate_quality_score(metrics) for metrics in quality_metrics] # 验证评分逻辑 assert scores[0] > scores[1] > scores[2] # 高影响因子期刊得分更高 assert 0 <= scores[0] <= 100 # 评分应该在0-100范围内 assert 0 <= scores[1] <= 100 assert 0 <= scores[2] <= 100 @pytest.mark.unit def test_quartile_normalization(self): """测试分区标准化逻辑""" from article_mcp.tools.core.quality_tools import _normalize_quartile quartile_cases = ["Q1", "Q2", "Q3", "Q4", "未知"] normalized_scores = [_normalize_quartile(q) for q in quartile_cases] # 验证分区评分 assert ( normalized_scores[0] > normalized_scores[1] > normalized_scores[2] > normalized_scores[3] ) assert normalized_scores[4] == 0 # 未知分区得分为0 @pytest.mark.unit def test_field_ranking_processing(self): """测试领域排名处理逻辑""" from article_mcp.tools.core.quality_tools import _process_field_ranking ranking_data = { "field_name": "Biology", "total_journals": 500, "journal_rank": 25, "quartile_distribution": {"Q1": 125, "Q2": 125, "Q3": 125, "Q4": 125}, } processed = _process_field_ranking(ranking_data) # 验证排名数据处理 assert "percentile" in processed assert processed["percentile"] == (25 / 500) * 100 # 5th percentile assert "quartile_info" in processed class TestBatchToolsCore: """测试批量工具核心逻辑""" @pytest.mark.unit def test_json_export_logic(self): """测试JSON导出逻辑""" import json import tempfile from pathlib import Path from article_mcp.tools.core.batch_tools import _export_to_json test_results = { "success": True, "merged_results": [ { "title": "Test Article", "authors": ["Test Author"], "doi": "10.1234/test.2023", } ], "total_count": 1, } with tempfile.TemporaryDirectory() as temp_dir: output_path = Path(temp_dir) / "test_export.json" logger = Mock() # 测试导出 records_count = _export_to_json(test_results, output_path, True, logger) # 验证导出结果 assert records_count == 1 assert output_path.exists() # 验证JSON内容 with open(output_path, encoding="utf-8") as f: exported_data = json.load(f) assert "export_metadata" in exported_data assert "results" in exported_data assert exported_data["export_metadata"]["total_records"] == 1 @pytest.mark.unit def test_csv_export_logic(self): """测试CSV导出逻辑""" import csv import tempfile from pathlib import Path from article_mcp.tools.core.batch_tools import _export_to_csv test_results = { "success": True, "merged_results": [ { "title": "Test Article 1", "authors": [{"name": "Author 1"}, {"name": "Author 2"}], "doi": "10.1234/test1.2023", "journal": "Test Journal", "publication_date": "2023-01-01", "abstract": "Test abstract 1", }, { "title": "Test Article 2", "authors": [{"name": "Author 3"}], "doi": "10.5678/test2.2023", "journal": "Another Journal", "publication_date": "2023-06-15", "abstract": "Test abstract 2", }, ], "total_count": 2, } with tempfile.TemporaryDirectory() as temp_dir: output_path = Path(temp_dir) / "test_export.csv" logger = Mock() # 测试导出 records_count = _export_to_csv(test_results, output_path, True, logger) # 验证导出结果 assert records_count == 2 assert output_path.exists() # 验证CSV内容 with open(output_path, encoding="utf-8", newline="") as f: reader = csv.DictReader(f) rows = list(reader) assert len(rows) == 2 assert "title" in rows[0] assert "authors" in rows[0] assert "Test Article 1" in rows[0]["title"] assert "Author 1; Author 2" in rows[0]["authors"] @pytest.mark.unit def test_export_path_generation(self): """测试导出路径生成逻辑""" import tempfile from pathlib import Path from article_mcp.tools.core.batch_tools import _generate_export_path with tempfile.TemporaryDirectory() as temp_dir: # 测试自动路径生成 auto_path = _generate_export_path(None, "json", temp_dir) assert auto_path.suffix == ".json" assert auto_path.parent == Path(temp_dir) # 测试指定路径 custom_path = _generate_export_path("custom_export.xlsx", "excel", temp_dir) assert custom_path.name == "custom_export.xlsx" assert custom_path.suffix == ".xlsx" @pytest.mark.unit def test_file_size_calculation(self): """测试文件大小计算逻辑""" import tempfile from pathlib import Path from article_mcp.tools.core.batch_tools import _calculate_file_size # 创建测试文件 with tempfile.NamedTemporaryFile(mode="w", delete=False, suffix=".txt") as f: f.write("Test content for file size calculation") temp_path = Path(f.name) try: # 测试文件大小计算 size_info = _calculate_file_size(temp_path) assert "size" in size_info assert "formatted_size" in size_info assert size_info["size"] > 0 assert "B" in size_info["formatted_size"] or "KB" in size_info["formatted_size"] finally: temp_path.unlink() class TestToolIntegration: """工具集成测试""" @pytest.mark.unit def test_cross_tool_data_flow(self): """测试跨工具数据流""" # 模拟从搜索到导出的完整数据流 search_results = { "success": True, "merged_results": [ { "title": "Research Article", "authors": ["Researcher"], "doi": "10.9999/research.2023", "journal": "High Impact Journal", } ], "total_count": 1, } # 测试数据在不同工具间的传递 assert "merged_results" in search_results assert len(search_results["merged_results"]) > 0 # 模拟文章详情获取 article = search_results["merged_results"][0] assert "doi" in article assert article["doi"] == "10.9999/research.2023" # 模拟参考文献获取 ref_params = { "identifier": article["doi"], "id_type": "doi", "max_results": 10, } assert ref_params["identifier"] == article["doi"] # 模拟导出参数 export_params = { "results": search_results, "format_type": "json", "include_metadata": True, } assert export_params["results"] == search_results @pytest.mark.unit def test_error_propagation(self): """测试错误传播机制""" # 模拟错误在不同工具间的传播 search_error = { "success": False, "error": "API request failed", "error_type": "RequestException", } # 验证错误信息传递 assert not search_error["success"] assert "error" in search_error assert "error_type" in search_error # 模拟错误处理 def handle_tool_error(error_result): return { "success": False, "user_message": f"操作失败: {error_result.get('error', '未知错误')}", "error_code": error_result.get("error_type", "UnknownError"), } user_message = handle_tool_error(search_error) assert "操作失败" in user_message["user_message"] assert user_message["error_code"] == "RequestException"

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/gqy20/article-mcp'

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