MCP Server Neurolorap

"""Unit tests for main server functionality.""" import os from collections.abc import Generator from pathlib import Path from typing import Any from unittest.mock import AsyncMock, MagicMock, NonCallableMock, patch import pytest from mcp_server_neurolorap.server import create_server, get_project_root class ToolMock(AsyncMock): """Custom AsyncMock that matches the expected tool callable type.""" def __init__( self, spec: list[str] | object | type[object] | None = None, wraps: Any | None = None, name: str | None = None, spec_set: list[str] | object | type[object] | None = None, parent: NonCallableMock | None = None, _spec_state: Any | None = None, _new_name: str = "", _new_parent: NonCallableMock | None = None, _spec_as_instance: bool = False, _eat_self: bool | None = None, unsafe: bool = False, **kwargs: Any, ) -> None: super().__init__( spec=spec, wraps=wraps, name=name, spec_set=spec_set, parent=parent, _spec_state=_spec_state, _new_name=_new_name, _new_parent=_new_parent, _spec_as_instance=_spec_as_instance, _eat_self=_eat_self, unsafe=unsafe, **kwargs, ) self._collector: AsyncMock | None = None def set_collector(self, collector: AsyncMock | None) -> None: """Set the collector instance for this tool.""" self._collector = collector class MockFastMCP: """Mock FastMCP server.""" def __init__(self, name: str) -> None: self.name = name self.tools = {"code_collector": ToolMock()} self.tool_called = False self.info = MagicMock() self.debug = MagicMock() self.error = MagicMock() self._tool_error: Exception | None = None def set_tool_error(self, error: Exception) -> None: """Set error to be raised during tool registration.""" self._tool_error = error def tool(self, *args: Any, **kwargs: Any) -> Any: """Tool decorator.""" if self._tool_error is not None: self.error( "Failed to initialize server: Test error", exc_info=True ) raise self._tool_error def decorator(func: Any) -> Any: self.tool_called = True self.debug("Registering tool: code_collector") self.info("Starting MCP server: neurolorap") return self.tools["code_collector"] return decorator def run(self) -> None | Any: """Run the server.""" return MagicMock() @pytest.fixture def mock_fastmcp() -> Generator[MockFastMCP, None, None]: """Mock FastMCP server.""" with patch("mcp_server_neurolorap.server.FastMCP") as mock: mock_server = MockFastMCP("neurolorap") mock.return_value = mock_server yield mock_server @pytest.fixture def mock_logger() -> Generator[MagicMock, None, None]: """Mock logger.""" with patch("mcp_server_neurolorap.server.logger") as mock_logger: yield mock_logger def test_create_server(mock_fastmcp: MockFastMCP) -> None: """Test server creation and configuration.""" server = create_server() assert server.name == "neurolorap" assert server.tool_called assert "code_collector" in server.tools def test_project_root_environment(mock_logger: MagicMock) -> None: """Test project root environment variable handling.""" # Test with environment variable set test_path = "/test/path" with patch.dict(os.environ, {"MCP_PROJECT_ROOT": test_path}, clear=True): root = get_project_root() assert str(root) == test_path mock_logger.info.assert_not_called() # Test with environment variable not set with patch.dict(os.environ, {}, clear=True): with patch("pathlib.Path.cwd") as mock_cwd: mock_cwd.return_value = Path("/current/dir") root = get_project_root() assert str(root) == "/current/dir" mock_logger.info.assert_called_with( "Set MCP_PROJECT_ROOT to: %s", Path("/current/dir") ) @pytest.mark.asyncio async def test_server_initialization(mock_fastmcp: MockFastMCP) -> None: """Test server initialization process.""" server = create_server() assert server.name == "neurolorap" mock_fastmcp.info.assert_called_with("Starting MCP server: neurolorap") @pytest.mark.asyncio async def test_server_tool_registration(mock_fastmcp: MockFastMCP) -> None: """Test tool registration process.""" server = create_server() assert "code_collector" in server.tools mock_fastmcp.debug.assert_any_call("Registering tool: code_collector") @pytest.mark.asyncio async def test_server_error_handling(mock_fastmcp: MockFastMCP) -> None: """Test server error handling.""" # Test initialization error test_error = Exception("Test error") mock_fastmcp.set_tool_error(test_error) with pytest.raises(Exception) as exc_info: create_server() assert str(exc_info.value) == "Test error" mock_fastmcp.error.assert_called_with( "Failed to initialize server: Test error", exc_info=True )