Skip to main content
Glama

Noctua MCP Server

Official
by geneontology
test_unit.py18.2 kB
""" Unit tests for the noctua-mcp server components. These tests do not require the MCP client and test individual functions directly. """ import os from unittest.mock import Mock, patch import pytest def test_imports(): """Test that all modules can be imported.""" def test_barista_compatibility(): """Test that noctua classes can be imported directly.""" from noctua import BaristaClient, BaristaResponse, BaristaError # These should be imported from noctua assert BaristaClient is not None assert BaristaResponse is not None assert BaristaError is not None @pytest.mark.asyncio async def test_get_client_singleton(): """Test that get_client returns a singleton instance.""" import noctua_mcp.mcp_server as mcp_server with patch("noctua_mcp.mcp_server.BaristaClient") as MockClient: mock_instance = Mock() MockClient.return_value = mock_instance # Reset global client mcp_server._client = None # First call should create client client1 = mcp_server.get_client() assert client1 == mock_instance MockClient.assert_called_once() # Second call should return same instance client2 = mcp_server.get_client() assert client2 == client1 MockClient.assert_called_once() # Still only called once @pytest.mark.asyncio async def test_configure_token_resets_client(): """Test that configure_token resets the client instance.""" import noctua_mcp.mcp_server as mcp_server with patch("noctua_mcp.mcp_server.BaristaClient") as MockClient: mock_instance = Mock() MockClient.return_value = mock_instance # Set initial client mcp_server._client = mock_instance # Configure new token - access the wrapped function result = await mcp_server.configure_token.fn("new-token") # Client should be reset to None assert mcp_server._client is None assert os.environ.get("BARISTA_TOKEN") == "new-token" assert result["success"] is True assert result["configured"] is True # Clean up if "BARISTA_TOKEN" in os.environ: del os.environ["BARISTA_TOKEN"] @pytest.mark.asyncio async def test_add_individual_parameters(): """Test that add_individual passes correct parameters.""" import noctua_mcp.mcp_server as mcp_server with patch("noctua_mcp.mcp_server.BaristaClient") as MockClient: mock_instance = Mock() MockClient.return_value = mock_instance from noctua import BaristaResponse mock_resp = Mock(spec=BaristaResponse) mock_resp.raw = {"test": "response"} # Update validation properties mock_resp.validation_failed = False mock_resp.validation_reason = None mock_resp.error = None mock_resp.success = True mock_resp.individual_id = "var1" mock_resp.class_curie = "GO:0003674" mock_resp.assign_var = "var1" # Mock add_individual_validated mock_instance.add_individual_validated.return_value = mock_resp mcp_server._client = None result = await mcp_server.add_individual.fn( "model123", "GO:0003674", "molecular_function", "var1" ) # Should use add_individual_validated mock_instance.add_individual_validated.assert_called_once_with( "model123", "GO:0003674", {"id": "GO:0003674", "label": "molecular_function"}, "var1" ) assert result["success"] is True assert result["individual_id"] == "var1" assert result["class_curie"] == "GO:0003674" assert result["assign_var"] == "var1" @pytest.mark.asyncio async def test_add_evidence_to_fact_parameters(): """Test that add_evidence_to_fact passes correct parameters.""" import noctua_mcp.mcp_server as mcp_server with patch("noctua_mcp.mcp_server.BaristaClient") as MockClient: mock_instance = Mock() MockClient.return_value = mock_instance from noctua import BaristaResponse mock_resp = Mock(spec=BaristaResponse) mock_resp.validation_failed = False mock_resp.validation_reason = None mock_resp.error = None mock_resp.success = True mock_resp.raw = {"test": "response"} mock_reqs = [{"req": "1"}, {"req": "2"}] mock_instance.req_add_evidence_to_fact.return_value = mock_reqs mock_instance.m3_batch.return_value = mock_resp mcp_server._client = None result = await mcp_server.add_evidence_to_fact.fn( model_id="model123", subject_id="subj1", object_id="obj1", predicate_id="RO:0002333", eco_id="ECO:0000353", sources=["PMID:12345"], with_from=["UniProtKB:P12345"] ) mock_instance.req_add_evidence_to_fact.assert_called_once_with( "model123", "subj1", "obj1", "RO:0002333", "ECO:0000353", ["PMID:12345"], ["UniProtKB:P12345"] ) mock_instance.m3_batch.assert_called_once_with(mock_reqs) assert result["success"] is True @pytest.mark.asyncio async def test_model_summary_calculations(): """Test that model_summary correctly calculates statistics.""" import noctua_mcp.mcp_server as mcp_server with patch("noctua_mcp.mcp_server.BaristaClient") as MockClient: mock_instance = Mock() MockClient.return_value = mock_instance from noctua import BaristaResponse mock_resp = Mock(spec=BaristaResponse) mock_resp.validation_failed = False mock_resp.validation_reason = None mock_resp.error = None mock_resp.success = True mock_resp.individuals = [ {"id": "i1"}, {"id": "i2"}, {"id": "i3"}, {"id": "i4"} ] mock_resp.facts = [ {"property": "RO:0002333"}, {"property": "RO:0002333"}, {"property": "RO:0002333"}, {"property": "RO:0002432"}, {"property": "RO:0002434"}, ] mock_resp.model_state = "development" mock_instance.get_model.return_value = mock_resp mcp_server._client = None result = await mcp_server.model_summary.fn("model123") assert result["model_id"] == "model123" assert result["state"] == "development" assert result["individual_count"] == 4 assert result["fact_count"] == 5 assert result["predicate_distribution"]["RO:0002333"] == 3 assert result["predicate_distribution"]["RO:0002432"] == 1 assert result["predicate_distribution"]["RO:0002434"] == 1 @pytest.mark.asyncio async def test_model_summary_error_handling(): """Test that model_summary handles errors gracefully.""" import noctua_mcp.mcp_server as mcp_server with patch("noctua_mcp.mcp_server.BaristaClient") as MockClient: mock_instance = Mock() MockClient.return_value = mock_instance from noctua import BaristaResponse mock_resp = Mock(spec=BaristaResponse) mock_resp.validation_failed = True mock_resp.validation_reason = "Model not found" mock_resp.error = "not found" mock_resp.success = False mock_resp.raw = {"error": "not found"} mock_instance.get_model.return_value = mock_resp mcp_server._client = None result = await mcp_server.model_summary.fn("invalid") assert "error" in result assert result["error"] == "Validation failed" assert result["reason"] == "Model not found" @pytest.mark.asyncio async def test_add_basic_pathway_request_sequence(): """Test that add_basic_pathway creates the correct sequence of requests.""" import noctua_mcp.mcp_server as mcp_server with patch("noctua_mcp.mcp_server.BaristaClient") as MockClient: mock_instance = Mock() MockClient.return_value = mock_instance from noctua import BaristaResponse mock_resp = Mock(spec=BaristaResponse) mock_resp.validation_failed = False mock_resp.validation_reason = None mock_resp.error = None mock_resp.success = True mock_resp.raw = {"success": True} # Track all calls individual_calls = [] fact_calls = [] def track_individual(*args): individual_calls.append(args) return {"entity": "individual", "args": args} def track_fact(*args): fact_calls.append(args) return {"entity": "edge", "args": args} mock_instance.req_add_individual.side_effect = track_individual mock_instance.req_add_fact.side_effect = track_fact mock_instance.m3_batch.return_value = mock_resp mcp_server._client = None await mcp_server.add_basic_pathway.fn( model_id="model123", pathway_curie="GO:0016055", pathway_label="Wnt signaling pathway", mf_curie="GO:0003674", mf_label="molecular_function", gene_product_curie="UniProtKB:P38398", gene_product_label="BRCA1", cc_curie="GO:0005575", cc_label="cellular_component" ) # Check individuals created assert len(individual_calls) == 4 assert ("model123", "GO:0003674", "mf1") in individual_calls assert ("model123", "UniProtKB:P38398", "gp1") in individual_calls assert ("model123", "GO:0016055", "bp1") in individual_calls assert ("model123", "GO:0005575", "cc1") in individual_calls # Check facts created assert len(fact_calls) == 3 assert ("model123", "mf1", "gp1", "RO:0002333") in fact_calls # enabled_by assert ("model123", "mf1", "cc1", "BFO:0000066") in fact_calls # occurs_in assert ("model123", "mf1", "bp1", "BFO:0000050") in fact_calls # part_of def test_prompts(): """Test that prompt functions return expected content.""" import noctua_mcp.mcp_server as mcp_server activity_prompt = mcp_server.create_basic_activity.fn() assert isinstance(activity_prompt, str) assert "add_individual" in activity_prompt assert "molecular function" in activity_prompt assert "RO:0002333" in activity_prompt evidence_prompt = mcp_server.add_evidence_prompt.fn() assert isinstance(evidence_prompt, str) assert "add_evidence_to_fact" in evidence_prompt assert "ECO" in evidence_prompt assert "PMID" in evidence_prompt def test_cli_imports(): """Test that CLI module can be imported and has expected functions.""" from noctua_mcp.cli import main, app, serve, version assert main is not None assert app is not None assert serve is not None assert version is not None @pytest.mark.parametrize("model_id,expected", [ ("gomodel:12345", "gomodel:12345"), ("gomodel:abcde", "gomodel:abcde"), ("test123", "test123"), ]) @pytest.mark.asyncio async def test_get_model_with_different_ids(model_id, expected): """Test get_model with various model IDs.""" import noctua_mcp.mcp_server as mcp_server with patch("noctua_mcp.mcp_server.BaristaClient") as MockClient: mock_instance = Mock() MockClient.return_value = mock_instance from noctua import BaristaResponse mock_resp = Mock(spec=BaristaResponse) mock_resp.raw = {"model_id": expected} mock_instance.get_model.return_value = mock_resp mcp_server._client = None result = await mcp_server.get_model.fn(model_id) mock_instance.get_model.assert_called_once_with(model_id) assert result["model_id"] == expected @pytest.mark.asyncio async def test_search_models_basic(): """Test search_models basic functionality.""" import noctua_mcp.mcp_server as mcp_server with patch("noctua_mcp.mcp_server.BaristaClient") as MockClient: mock_instance = Mock() MockClient.return_value = mock_instance mock_results = { "models": [ {"id": "gomodel:001", "title": "Model 1", "state": "production"}, {"id": "gomodel:002", "title": "Model 2", "state": "development"} ], "total": 2 } mock_instance.list_models.return_value = mock_results mcp_server._client = None result = await mcp_server.search_models.fn() mock_instance.list_models.assert_called_once_with( title=None, state=None, contributor=None, group=None, pmid=None, gp=None, limit=50, offset=0 ) assert result == mock_results @pytest.mark.parametrize("filters,expected_args", [ ( {"title": "kinase", "state": "production"}, {"title": "kinase", "state": "production", "contributor": None, "group": None, "pmid": None, "gp": None} ), ( {"gene_product": "UniProtKB:P12345", "limit": 10}, {"title": None, "state": None, "contributor": None, "group": None, "pmid": None, "gp": "UniProtKB:P12345"} ), ( {"contributor": "https://orcid.org/0000-0002-6601-2165", "pmid": "PMID:12345"}, {"title": None, "state": None, "contributor": "https://orcid.org/0000-0002-6601-2165", "group": None, "pmid": "PMID:12345", "gp": None} ), ]) @pytest.mark.asyncio async def test_search_models_with_filters(filters, expected_args): """Test search_models with various filter combinations.""" import noctua_mcp.mcp_server as mcp_server with patch("noctua_mcp.mcp_server.BaristaClient") as MockClient: mock_instance = Mock() MockClient.return_value = mock_instance mock_results = {"models": [], "total": 0} mock_instance.list_models.return_value = mock_results mcp_server._client = None # Set defaults for limit and offset call_params = {**filters} if "limit" not in call_params: call_params["limit"] = 50 if "offset" not in call_params: call_params["offset"] = 0 result = await mcp_server.search_models.fn(**filters) # Build expected call args with defaults full_expected_args = {**expected_args} full_expected_args["limit"] = call_params.get("limit", 50) full_expected_args["offset"] = call_params.get("offset", 0) mock_instance.list_models.assert_called_once_with(**full_expected_args) assert result == mock_results @pytest.mark.asyncio async def test_search_models_exception_handling(): """Test that search_models handles exceptions gracefully.""" import noctua_mcp.mcp_server as mcp_server with patch("noctua_mcp.mcp_server.BaristaClient") as MockClient: mock_instance = Mock() MockClient.return_value = mock_instance mock_instance.list_models.side_effect = RuntimeError("API connection failed") mcp_server._client = None result = await mcp_server.search_models.fn(title="test") assert "error" in result assert result["error"] == "Failed to search models" assert "API connection failed" in result["message"] @pytest.mark.asyncio async def test_search_bioentities_basic(): """Test search_bioentities basic functionality.""" import noctua_mcp.mcp_server as mcp_server with patch("noctua_mcp.mcp_server.AmigoClient") as MockAmigoClient: mock_client = Mock() MockAmigoClient.return_value.__enter__.return_value = mock_client from noctua.amigo import BioentityResult mock_results = [ BioentityResult( id="UniProtKB:P01308", label="INS", name="insulin", type="protein", taxon="NCBITaxon:9606", taxon_label="Homo sapiens", source="UniProtKB", raw={} ) ] mock_client.search_bioentities.return_value = mock_results result = await mcp_server.search_bioentities.fn() mock_client.search_bioentities.assert_called_once_with( text=None, taxon=None, bioentity_type=None, source=None, limit=10, offset=0 ) assert "results" in result assert result["count"] == 1 @pytest.mark.parametrize("taxon_input,expected_taxon", [ ("9606", "NCBITaxon:9606"), ("NCBITaxon:9606", "NCBITaxon:9606"), ("10090", "NCBITaxon:10090"), ("NCBITaxon:10090", "NCBITaxon:10090"), (None, None), ]) @pytest.mark.asyncio async def test_search_bioentities_taxon_normalization_unit(taxon_input, expected_taxon): """Test that taxon IDs are normalized correctly.""" import noctua_mcp.mcp_server as mcp_server with patch("noctua_mcp.mcp_server.AmigoClient") as MockAmigoClient: mock_client = Mock() MockAmigoClient.return_value.__enter__.return_value = mock_client mock_client.search_bioentities.return_value = [] await mcp_server.search_bioentities.fn(taxon=taxon_input) mock_client.search_bioentities.assert_called_once_with( text=None, taxon=expected_taxon, bioentity_type=None, source=None, limit=10, offset=0 ) @pytest.mark.asyncio async def test_search_bioentities_all_parameters(): """Test search_bioentities with all parameters.""" import noctua_mcp.mcp_server as mcp_server with patch("noctua_mcp.mcp_server.AmigoClient") as MockAmigoClient: mock_client = Mock() MockAmigoClient.return_value.__enter__.return_value = mock_client mock_client.search_bioentities.return_value = [] await mcp_server.search_bioentities.fn( text="kinase", taxon="9606", bioentity_type="protein", source="UniProtKB", limit=20, offset=10 ) mock_client.search_bioentities.assert_called_once_with( text="kinase", taxon="NCBITaxon:9606", bioentity_type="protein", source="UniProtKB", limit=20, offset=10 )

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/geneontology/noctua-mcp'

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