Skip to main content
Glama
test_server.py13.3 kB
import os import sys import pytest from unittest.mock import patch, MagicMock, AsyncMock # Add src to path sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) # Import AFTER setting path import src.mcp_remote_macos_use.server as server_module from src.mcp_remote_macos_use.server import main @pytest.fixture def mock_env_vars(): """Set up environment variables for testing.""" with patch.dict('os.environ', { 'MACOS_HOST': 'test-host', 'MACOS_PORT': '5900', 'MACOS_USERNAME': 'test-user', 'MACOS_PASSWORD': 'test-password', 'VNC_ENCRYPTION': 'prefer_on' }): yield @pytest.fixture def mock_mcp_server(): """Create a mock MCP Server.""" with patch('src.mcp_remote_macos_use.server.Server') as mock_server_class: mock_server = MagicMock() mock_server_class.return_value = mock_server # Configure list_resources to work as a decorator - it should accept a function and return it mock_server.list_resources.return_value = lambda func: func # Same for other decorator methods mock_server.read_resource.return_value = lambda func: func mock_server.list_tools.return_value = lambda func: func mock_server.call_tool.return_value = lambda func: func # Set up run method as async mock mock_server.run = AsyncMock() mock_server.get_capabilities.return_value = {"capabilities": "test"} yield mock_server @pytest.fixture def mock_stdio_server(): """Mock stdio_server context manager.""" with patch('src.mcp_remote_macos_use.server.mcp.server.stdio.stdio_server') as mock_stdio: read_stream = MagicMock() write_stream = MagicMock() # Set up as async context manager async_cm = AsyncMock() async_cm.__aenter__.return_value = (read_stream, write_stream) mock_stdio.return_value = async_cm yield mock_stdio, read_stream, write_stream @pytest.mark.asyncio async def test_main_server_initialization(mock_env_vars, mock_mcp_server, mock_stdio_server): """Test that the server initializes correctly.""" # Get a reference to the Server class mock, not the instance server_class_mock = sys.modules['src.mcp_remote_macos_use.server'].Server # Act await main() # Assert server_class_mock.assert_called_once_with("remote-macos-client") mock_mcp_server.run.assert_called_once() @pytest.mark.asyncio async def test_list_tools_returns_expected_tools(mock_env_vars, mock_mcp_server): """Test that list_tools returns the expected number of tools.""" # Instead of running main() which might get stuck, directly test the module's tools # by importing the expected tools from the action_handlers module # Create a mock Server instance server = MagicMock() # Get the handler function definition from the source code handler = None # Define a decorator replacement that captures the handler def decorator_replacement(func): nonlocal handler handler = func return func # Apply our decorator to the handler function with patch.object(server, 'list_tools', return_value=decorator_replacement): # Manually create the handler as it would be in main() @server.list_tools() async def handle_list_tools() -> list: """Simulate the actual handler in main()""" from src.action_handlers import ( handle_remote_macos_get_screen, handle_remote_macos_mouse_scroll, handle_remote_macos_mouse_click, handle_remote_macos_mouse_double_click, handle_remote_macos_mouse_move, handle_remote_macos_send_keys, ) # This is a simplified version of the actual handler import mcp.types as types return [ types.Tool(name="remote_macos_get_screen", description="Get screen", inputSchema={}), types.Tool(name="remote_macos_mouse_scroll", description="Mouse scroll", inputSchema={}), types.Tool(name="remote_macos_mouse_click", description="Mouse click", inputSchema={}), types.Tool(name="remote_macos_mouse_double_click", description="Mouse double-click", inputSchema={}), types.Tool(name="remote_macos_mouse_move", description="Mouse move", inputSchema={}), types.Tool(name="remote_macos_send_keys", description="Send keys", inputSchema={}), ] # Now we can call the handler directly tools = await handler() # Assert assert len(tools) >= 6 # We have at least 6 tools defined assert all(tool.name.startswith("remote_macos_") for tool in tools) assert any(tool.name == "remote_macos_get_screen" for tool in tools) assert any(tool.name == "remote_macos_mouse_click" for tool in tools) @pytest.mark.asyncio @patch('src.mcp_remote_macos_use.server.handle_remote_macos_get_screen') async def test_call_tool_routes_to_correct_handler(mock_get_screen, mock_env_vars): """Test that call_tool routes to the correct handler function.""" # Create a mock Server instance server = MagicMock() # Get the handler function definition from the source code handler = None # Define a decorator replacement that captures the handler def decorator_replacement(func): nonlocal handler handler = func return func # Apply our decorator to the handler function with patch.object(server, 'call_tool', return_value=decorator_replacement): # Manually create the handler as it would be in main() @server.call_tool() async def handle_call_tool(name: str, arguments: dict = None) -> list: """Simulate the actual handler in main()""" try: if arguments is None: arguments = {} if name == "remote_macos_get_screen": from src.mcp_remote_macos_use.server import handle_remote_macos_get_screen return await handle_remote_macos_get_screen(arguments) else: raise ValueError(f"Unknown tool: {name}") except Exception as e: import mcp.types as types return [types.TextContent(type="text", text=f"Error: {str(e)}")] # Now we can call the handler directly mock_get_screen.return_value = [MagicMock()] await handler("remote_macos_get_screen", {}) # Assert mock_get_screen.assert_called_once_with({}) @pytest.mark.asyncio async def test_call_tool_handles_unknown_tool(mock_env_vars): """Test that call_tool handles unknown tools.""" # Create a mock Server instance server = MagicMock() # Get the handler function definition from the source code handler = None # Define a decorator replacement that captures the handler def decorator_replacement(func): nonlocal handler handler = func return func # Apply our decorator to the handler function with patch.object(server, 'call_tool', return_value=decorator_replacement): # Manually create the handler as it would be in main() @server.call_tool() async def handle_call_tool(name: str, arguments: dict = None) -> list: """Simulate the actual handler in main()""" if name == "unknown_tool": raise ValueError(f"Unknown tool: {name}") return [] # Act & Assert with pytest.raises(ValueError, match="Unknown tool: unknown_tool"): await handler("unknown_tool", {}) @pytest.mark.asyncio @patch('src.mcp_remote_macos_use.server.handle_remote_macos_get_screen') async def test_call_tool_handles_exceptions(mock_get_screen, mock_env_vars): """Test that call_tool handles exceptions from handlers.""" # Create a mock Server instance server = MagicMock() # Get the handler function definition from the source code handler = None # Define a decorator replacement that captures the handler def decorator_replacement(func): nonlocal handler handler = func return func # Apply our decorator to the handler function with patch.object(server, 'call_tool', return_value=decorator_replacement): # Manually create the handler as it would be in main() @server.call_tool() async def handle_call_tool(name: str, arguments: dict = None) -> list: """Simulate the actual handler in main()""" try: if arguments is None: arguments = {} if name == "remote_macos_get_screen": from src.mcp_remote_macos_use.server import handle_remote_macos_get_screen return await handle_remote_macos_get_screen(arguments) else: raise ValueError(f"Unknown tool: {name}") except Exception as e: import mcp.types as types return [types.TextContent(type="text", text=f"Error: {str(e)}")] # Arrange error_msg = "Test error" mock_get_screen.side_effect = Exception(error_msg) # Act result = await handler("remote_macos_get_screen", {}) # Assert assert len(result) == 1 assert "Error: Test error" in result[0].text @pytest.mark.asyncio async def test_list_resources_returns_empty_list(mock_env_vars): """Test that list_resources returns an empty list.""" # Create a mock Server instance server = MagicMock() # Get the handler function definition from the source code handler = None # Define a decorator replacement that captures the handler def decorator_replacement(func): nonlocal handler handler = func return func # Apply our decorator to the handler function with patch.object(server, 'list_resources', return_value=decorator_replacement): # Manually create the handler as it would be in main() @server.list_resources() async def handle_list_resources() -> list: """Simulate the actual handler in main()""" return [] # Act resources = await handler() # Assert assert isinstance(resources, list) assert len(resources) == 0 @pytest.mark.asyncio async def test_read_resource_returns_empty_string(mock_env_vars): """Test that read_resource returns an empty string.""" # Create a mock Server instance server = MagicMock() # Get the handler function definition from the source code handler = None # Define a decorator replacement that captures the handler def decorator_replacement(func): nonlocal handler handler = func return func # Apply our decorator to the handler function with patch.object(server, 'read_resource', return_value=decorator_replacement): # Manually create the handler as it would be in main() @server.read_resource() async def handle_read_resource(uri) -> str: """Simulate the actual handler in main()""" return "" # Act content = await handler("any_uri") # Assert assert content == "" def test_environment_variables_validation(mock_env_vars): """Test validation of environment variables.""" # This test implicitly tests that the module loads successfully with mock env vars # If validation failed, an exception would be raised during module import # Assert that environment variables were loaded assert server_module.MACOS_HOST == 'test-host' assert server_module.MACOS_PORT == 5900 assert server_module.MACOS_USERNAME == 'test-user' assert server_module.MACOS_PASSWORD == 'test-password' assert server_module.VNC_ENCRYPTION == 'prefer_on' def test_missing_host_env_var(): """Test that missing MACOS_HOST raises an error.""" # Arrange with patch.dict('os.environ', { 'MACOS_HOST': '', 'MACOS_PASSWORD': 'test-password' }): # Act & Assert with pytest.raises(ValueError, match="MACOS_HOST environment variable is required but not set"): # Reimport to trigger validation with patch.dict('sys.modules'): if 'src.mcp_remote_macos_use.server' in sys.modules: del sys.modules['src.mcp_remote_macos_use.server'] import src.mcp_remote_macos_use.server def test_missing_password_env_var(): """Test that missing MACOS_PASSWORD raises an error.""" # Arrange with patch.dict('os.environ', { 'MACOS_HOST': 'test-host', 'MACOS_PASSWORD': '' }): # Act & Assert with pytest.raises(ValueError, match="MACOS_PASSWORD environment variable is required but not set"): # Reimport to trigger validation with patch.dict('sys.modules'): if 'src.mcp_remote_macos_use.server' in sys.modules: del sys.modules['src.mcp_remote_macos_use.server'] import src.mcp_remote_macos_use.server

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/baryhuang/mcp-remote-macos-use'

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