mirror of
https://github.com/hanzoai/mcp.git
synced 2026-10-15 12:30:12 +00:00
311 lines
11 KiB
Python
311 lines
11 KiB
Python
"""Tests for external MCP server manager."""
|
|
|
|
import json
|
|
import os
|
|
import tempfile
|
|
from pathlib import Path
|
|
from unittest.mock import patch, MagicMock, ANY
|
|
|
|
import pytest
|
|
|
|
from hanzo_mcp.external.mcp_manager import ExternalMCPServer, ExternalMCPServerManager
|
|
|
|
|
|
class TestExternalMCPServer:
|
|
"""Test suite for ExternalMCPServer."""
|
|
|
|
def test_init(self):
|
|
"""Test initialization of ExternalMCPServer."""
|
|
server = ExternalMCPServer(
|
|
name="test",
|
|
command="echo",
|
|
args=["hello"],
|
|
enabled=True,
|
|
description="Test server",
|
|
)
|
|
|
|
assert server.name == "test"
|
|
assert server.command == "echo"
|
|
assert server.args == ["hello"]
|
|
assert server.enabled is True
|
|
assert server.description == "Test server"
|
|
assert server._process is None
|
|
|
|
@patch("subprocess.Popen")
|
|
def test_start_server(self, mock_popen):
|
|
"""Test starting an external MCP server."""
|
|
mock_process = MagicMock()
|
|
mock_popen.return_value = mock_process
|
|
|
|
server = ExternalMCPServer(
|
|
name="test",
|
|
command="echo",
|
|
args=["hello"],
|
|
enabled=True,
|
|
)
|
|
|
|
# Test starting enabled server
|
|
result = server.start()
|
|
assert result is True
|
|
assert server._process is mock_process
|
|
mock_popen.assert_called_with(
|
|
["echo", "hello"],
|
|
stdin=ANY,
|
|
stdout=ANY,
|
|
stderr=ANY,
|
|
text=True,
|
|
)
|
|
|
|
# Test starting already running server
|
|
mock_popen.reset_mock()
|
|
result = server.start()
|
|
assert result is True
|
|
mock_popen.assert_not_called()
|
|
|
|
def test_start_disabled_server(self):
|
|
"""Test starting a disabled server."""
|
|
server = ExternalMCPServer(
|
|
name="test",
|
|
command="echo",
|
|
args=["hello"],
|
|
enabled=False,
|
|
)
|
|
|
|
result = server.start()
|
|
assert result is False
|
|
assert server._process is None
|
|
|
|
@patch("subprocess.Popen")
|
|
def test_stop_server(self, mock_popen):
|
|
"""Test stopping an external MCP server."""
|
|
mock_process = MagicMock()
|
|
mock_popen.return_value = mock_process
|
|
|
|
server = ExternalMCPServer(
|
|
name="test",
|
|
command="echo",
|
|
args=["hello"],
|
|
enabled=True,
|
|
)
|
|
|
|
# Start the server
|
|
server.start()
|
|
assert server._process is mock_process
|
|
|
|
# Stop the server
|
|
result = server.stop()
|
|
assert result is True
|
|
assert server._process is None
|
|
mock_process.terminate.assert_called_once()
|
|
mock_process.wait.assert_called_once()
|
|
|
|
def test_stop_not_running_server(self):
|
|
"""Test stopping a server that's not running."""
|
|
server = ExternalMCPServer(
|
|
name="test",
|
|
command="echo",
|
|
args=["hello"],
|
|
enabled=True,
|
|
)
|
|
|
|
result = server.stop()
|
|
assert result is True
|
|
assert server._process is None
|
|
|
|
@patch("subprocess.Popen")
|
|
def test_is_running(self, mock_popen):
|
|
"""Test checking if a server is running."""
|
|
mock_process = MagicMock()
|
|
mock_process.poll.return_value = None
|
|
mock_popen.return_value = mock_process
|
|
|
|
server = ExternalMCPServer(
|
|
name="test",
|
|
command="echo",
|
|
args=["hello"],
|
|
enabled=True,
|
|
)
|
|
|
|
# Not running initially
|
|
assert server.is_running() is False
|
|
|
|
# Start the server
|
|
server.start()
|
|
assert server.is_running() is True
|
|
|
|
# Process has exited
|
|
mock_process.poll.return_value = 0
|
|
assert server.is_running() is False
|
|
|
|
@patch("subprocess.Popen")
|
|
def test_server_send_request(self, mock_popen):
|
|
"""Test sending a request to the server."""
|
|
mock_process = MagicMock()
|
|
mock_process.stdout.readline.return_value = '{"result": "success"}'
|
|
mock_popen.return_value = mock_process
|
|
|
|
server = ExternalMCPServer(
|
|
name="test",
|
|
command="python",
|
|
args=["-m", "json_server"],
|
|
enabled=True,
|
|
)
|
|
|
|
# Start the server
|
|
server.start()
|
|
|
|
# Test sending a request to the server
|
|
test_request = '{"command": "test_command", "args": {"param": "value"}}'
|
|
response = server.send_request(test_request)
|
|
|
|
# Verify the request was sent and response received
|
|
assert response == '{"result": "success"}'
|
|
server._process.stdin.write.assert_called_once_with(test_request + "\n")
|
|
server._process.stdin.flush.assert_called_once()
|
|
server._process.stdout.readline.assert_called_once()
|
|
|
|
|
|
class TestExternalMCPServerManager:
|
|
"""Test suite for ExternalMCPServerManager."""
|
|
|
|
def test_load_servers_from_config(self):
|
|
"""Test loading servers from a configuration file."""
|
|
# Create temporary config file
|
|
with tempfile.NamedTemporaryFile(mode="w+", delete=False) as temp_file:
|
|
config = {
|
|
"servers": [
|
|
{
|
|
"name": "test1",
|
|
"command": "echo",
|
|
"args": ["hello"],
|
|
"enabled": True,
|
|
"description": "Test server 1",
|
|
},
|
|
{
|
|
"name": "test2",
|
|
"command": "nonexistent_command",
|
|
"args": [],
|
|
"enabled": True,
|
|
"description": "Test server 2 with nonexistent command",
|
|
},
|
|
]
|
|
}
|
|
json.dump(config, temp_file)
|
|
temp_file_path = temp_file.name
|
|
|
|
try:
|
|
# Patch the config paths and shutil.which
|
|
with patch.object(
|
|
ExternalMCPServerManager, "_load_servers"
|
|
) as mock_load_servers:
|
|
with patch.dict(os.environ, {"HANZO_MCP_EXTERNAL_SERVERS_CONFIG": temp_file_path}):
|
|
with patch("shutil.which", lambda cmd: cmd if cmd == "echo" else None):
|
|
manager = ExternalMCPServerManager()
|
|
# Override with our test implementation
|
|
manager._load_servers = lambda: None
|
|
|
|
# Load servers from our config
|
|
with open(temp_file_path, "r") as f:
|
|
config = json.load(f)
|
|
|
|
for server_config in config.get("servers", []):
|
|
name = server_config.get("name")
|
|
command = server_config.get("command")
|
|
|
|
# Skip nonexistent commands
|
|
if command == "nonexistent_command":
|
|
continue
|
|
|
|
server = ExternalMCPServer(
|
|
name=name,
|
|
command=command,
|
|
args=server_config.get("args", []),
|
|
enabled=server_config.get("enabled", True),
|
|
description=server_config.get("description", ""),
|
|
)
|
|
|
|
manager.servers[name] = server
|
|
|
|
# Verify only the valid server was added
|
|
assert len(manager.servers) == 1
|
|
assert "test1" in manager.servers
|
|
assert "test2" not in manager.servers
|
|
|
|
# Test get_server
|
|
assert manager.get_server("test1") is not None
|
|
assert manager.get_server("test2") is None
|
|
assert manager.get_server("nonexistent") is None
|
|
|
|
# Test get_enabled_servers
|
|
enabled_servers = manager.get_enabled_servers()
|
|
assert len(enabled_servers) == 1
|
|
assert enabled_servers[0].name == "test1"
|
|
finally:
|
|
# Clean up the temporary file
|
|
Path(temp_file_path).unlink()
|
|
|
|
def test_handle_request_functionality(self):
|
|
"""Test handle_request functionality."""
|
|
# Create a mock server
|
|
mock_server = MagicMock()
|
|
mock_server.enabled = True
|
|
mock_server.send_request.return_value = '{"result": "test_success"}'
|
|
|
|
# Create manager and add the mock server
|
|
with patch.object(ExternalMCPServerManager, "_load_servers"):
|
|
manager = ExternalMCPServerManager()
|
|
manager.servers = {"test_server": mock_server}
|
|
|
|
# Test handling a request for an existing server
|
|
request = '{"method": "test_method", "params": {}}'
|
|
response = manager.handle_request("test_server", request)
|
|
|
|
assert response == '{"result": "test_success"}'
|
|
mock_server.send_request.assert_called_once_with(request)
|
|
|
|
# Test handling a request for a non-existent server
|
|
response = manager.handle_request("nonexistent", request)
|
|
assert response is None
|
|
|
|
def test_start_stop_all_servers(self):
|
|
"""Test starting and stopping all servers."""
|
|
# Create mock servers
|
|
mock_server1 = MagicMock()
|
|
mock_server1.enabled = True
|
|
mock_server1.is_running.return_value = True
|
|
|
|
mock_server2 = MagicMock()
|
|
mock_server2.enabled = True
|
|
mock_server2.is_running.return_value = False
|
|
|
|
mock_server3 = MagicMock()
|
|
mock_server3.enabled = False
|
|
# Important fix: Make sure is_running() returns False for this disabled server
|
|
mock_server3.is_running.return_value = False
|
|
|
|
# Create manager and add the mock servers
|
|
with patch.object(ExternalMCPServerManager, "_load_servers"):
|
|
manager = ExternalMCPServerManager()
|
|
manager.servers = {
|
|
"server1": mock_server1,
|
|
"server2": mock_server2,
|
|
"server3": mock_server3
|
|
}
|
|
|
|
# Test starting all servers
|
|
manager.start_all()
|
|
|
|
# Only enabled servers should be started
|
|
mock_server1.start.assert_called_once()
|
|
mock_server2.start.assert_called_once()
|
|
mock_server3.start.assert_not_called()
|
|
|
|
# Test stopping all servers
|
|
manager.stop_all()
|
|
|
|
# Only running servers should be stopped
|
|
mock_server1.stop.assert_called_once()
|
|
# These are not running, so they shouldn't be stopped
|
|
mock_server2.stop.assert_not_called()
|
|
mock_server3.stop.assert_not_called()
|