Skip to main content
Glama

cBioPortal MCP Server

by pickleton89
test_server_lifecycle.py8 kB
#!/usr/bin/env python3 # Tests for server lifecycle management and tool registration import pytest import httpx from unittest.mock import AsyncMock # CBioPortalMCPServer import removed as it's not directly used by name in this file. # Fixtures from conftest.py provide instances of CBioPortalMCPServer. @pytest.mark.asyncio async def test_lifecycle_hooks_registered(cbioportal_server_instance): """Test that startup and shutdown hooks are correctly registered with FastMCP.""" server = cbioportal_server_instance assert server.startup in server.mcp.on_startup assert server.shutdown in server.mcp.on_shutdown @pytest.mark.asyncio async def test_tool_registration(cbioportal_server_instance): """Test that all intended public methods are registered as MCP tools and others are not.""" server = cbioportal_server_instance registered_tools = await server.mcp.get_tools() registered_tool_names = set(registered_tools) # Expected public API methods that should be registered (corrected list) expected_tools = { "paginate_results", "collect_all_results", "get_cancer_studies", "get_cancer_types", "get_samples_in_study", "search_genes", "search_studies", "get_molecular_profiles", "get_mutations_in_gene", "get_clinical_data", "get_study_details", "get_multiple_studies", "get_multiple_genes", "get_genes", "get_sample_list_id", "get_gene_panels_for_study", "get_gene_panel_details", } # Methods that should NOT be registered (internal, lifecycle, etc.) (corrected list) non_expected_tools = { "startup", "shutdown", "_make_api_request", "_register_tools", "_paginate_results", # _paginate_results is an internal helper, paginate_results (public) is expected "__init__", "_fetch_study_details_concurrently", "_fetch_gene_details_concurrently_batched", "client", # attribute "mcp", # attribute "base_url", # attribute } # Check that all expected tools are registered assert expected_tools.issubset(registered_tool_names), ( f"Missing tools: {expected_tools - registered_tool_names}" ) # Check that no non-expected tools are registered unexpectedly_registered = non_expected_tools.intersection(registered_tool_names) # Filter out any tools that might appear in both expected and non-expected due to naming conventions (e.g. _paginate_results vs paginate_results) # The critical check is that truly internal/private methods are not exposed. # The _register_tools method itself should handle its exclusion list correctly. # Here, we primarily ensure core lifecycle and private underscored methods are not in registered_tool_names. assert not unexpectedly_registered, ( f"Unexpectedly registered tools: {unexpectedly_registered}" ) # Optional: Check exact count if it's stable and known assert len(registered_tool_names) == len(expected_tools), ( f"Mismatch in tool count. Expected {len(expected_tools)}, got {len(registered_tool_names)}. " f"Registered: {sorted(list(registered_tool_names))}. Expected: {sorted(list(expected_tools))}" ) @pytest.mark.asyncio async def test_server_startup_initializes_async_client( cbioportal_server_instance_unstarted, mocker, # Added mocker fixture ): """Test that server.startup() initializes an httpx.AsyncClient and logs correctly.""" server = cbioportal_server_instance_unstarted assert server.api_client._client is None, ( "APIClient's client should be None before startup" ) mock_api_client_logger_info = mocker.patch("cbioportal_mcp.api_client.logger.info") mock_server_logger_info = mocker.patch("cbioportal_mcp.server.logger.info") await server.startup() assert isinstance(server.api_client._client, httpx.AsyncClient), ( "APIClient's client should be an httpx.AsyncClient after startup" ) assert server.api_client._client.timeout.read == 30.0 mock_api_client_logger_info.assert_any_call( f"APIClient's httpx.AsyncClient started with base_url: {server.base_url} and timeout: {server.api_client.client_timeout}s" ) mock_server_logger_info.assert_any_call( "cBioPortal MCP Server started, APIClient initialized." ) # Clean up the client created during this test if server.api_client and server.api_client._client: await server.api_client._client.aclose() # Set APIClient's client back to None to ensure test isolation server.api_client._client = None @pytest.mark.asyncio async def test_server_shutdown_closes_async_client( cbioportal_server_instance_unstarted, mocker, # Added mocker fixture ): """Test that server.shutdown() calls aclose() on the httpx.AsyncClient and logs correctly.""" server = cbioportal_server_instance_unstarted # Mock the client and its aclose method mock_async_client = AsyncMock(spec=httpx.AsyncClient) # Simulate server startup having initialized the APIClient's client server.api_client._client = mock_async_client mock_api_client_logger_info = mocker.patch("cbioportal_mcp.api_client.logger.info") mock_server_logger_info = mocker.patch("cbioportal_mcp.server.logger.info") await server.shutdown() mock_async_client.aclose.assert_called_once() mock_api_client_logger_info.assert_any_call("APIClient's httpx.AsyncClient closed.") mock_server_logger_info.assert_any_call( "cBioPortal MCP Server APIClient shut down." ) # Ensure client is set to None after shutdown as per CBioPortalMCPServer.shutdown logic # This depends on actual implementation; if shutdown doesn't set to None, adjust test # For now, assuming it might, or that it's good practice for the test to verify client state. # If CBioPortalMCPServer.shutdown does `self.client = None`, this assertion is valid. # If not, then perhaps the fixture should ensure a clean state or this assertion is removed. # Based on cbioportal_server.py, it does not set self.client to None after close. # So, we only care that aclose was called. @pytest.mark.asyncio async def test_server_shutdown_handles_no_client( cbioportal_server_instance_unstarted, mocker ): # Added mocker fixture """Test that server.shutdown() handles self.client being None gracefully and does not log closure.""" server = cbioportal_server_instance_unstarted assert server.api_client._client is None # Ensure APIClient's client is None mock_api_client_logger_info = mocker.patch("cbioportal_mcp.api_client.logger.info") mock_server_logger_info = mocker.patch("cbioportal_mcp.server.logger.info") try: await server.shutdown() # Should not raise an error except Exception as e: pytest.fail(f"server.shutdown() raised an exception with no client: {e}") # The server's shutdown log is unconditional after api_client.shutdown() completes. # api_client.shutdown() itself won't log if its internal client was None. mock_api_client_logger_info.assert_any_call( "APIClient's httpx.AsyncClient was already closed or not initialized." ) mock_server_logger_info.assert_any_call( "cBioPortal MCP Server APIClient shut down." ) @pytest.mark.asyncio async def test_initialization(cbioportal_server_instance_unstarted): server = cbioportal_server_instance_unstarted assert server.base_url == "http://mocked.cbioportal.org/api" assert ( server.api_client._client is None ) # APIClient's httpx.AsyncClient should be None initially assert server.mcp is not None registered_tools = await server.mcp.get_tools() # Convert tool objects to a set of names for easier assertion registered_tool_names = set(registered_tools) assert "get_cancer_studies" in registered_tool_names

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/pickleton89/cbioportal-mcp'

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