Skip to content

Commit 5a66a1e

Browse files
committed
added tests
1 parent a11b91c commit 5a66a1e

File tree

9 files changed

+663
-0
lines changed

9 files changed

+663
-0
lines changed

tests/mcp/__init__.py

Whitespace-only changes.

tests/mcp/test_mcp_tool.py

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
# tests/mcp/test_mcp_tool.py
2+
import pytest
3+
from unittest.mock import Mock, AsyncMock
4+
5+
from chuk_tool_processor.mcp.mcp_tool import MCPTool
6+
from chuk_tool_processor.mcp.stream_manager import StreamManager
7+
8+
9+
class TestMCPTool:
10+
"""Test MCPTool class."""
11+
12+
@pytest.fixture
13+
def mock_stream_manager(self):
14+
"""Mock StreamManager instance."""
15+
mock = Mock(spec=StreamManager)
16+
mock.call_tool = AsyncMock()
17+
return mock
18+
19+
@pytest.fixture
20+
def mcp_tool(self, mock_stream_manager):
21+
"""Create MCPTool instance."""
22+
return MCPTool("echo", mock_stream_manager)
23+
24+
@pytest.mark.asyncio
25+
async def test_execute_success(self, mcp_tool, mock_stream_manager):
26+
"""Test successful tool execution."""
27+
# Setup
28+
mock_stream_manager.call_tool.return_value = {
29+
"isError": False,
30+
"content": "Hello World"
31+
}
32+
33+
# Execute
34+
result = await mcp_tool.execute(message="Hello")
35+
36+
# Verify
37+
assert result == "Hello World"
38+
mock_stream_manager.call_tool.assert_called_once_with(
39+
tool_name="echo",
40+
arguments={"message": "Hello"}
41+
)
42+
43+
@pytest.mark.asyncio
44+
async def test_execute_error(self, mcp_tool, mock_stream_manager):
45+
"""Test tool execution with error."""
46+
# Setup
47+
mock_stream_manager.call_tool.return_value = {
48+
"isError": True,
49+
"error": "Connection failed"
50+
}
51+
52+
# Execute and verify error
53+
with pytest.raises(RuntimeError, match="Connection failed"):
54+
await mcp_tool.execute(message="Hello")
55+
56+
@pytest.mark.asyncio
57+
async def test_execute_unknown_error(self, mcp_tool, mock_stream_manager):
58+
"""Test tool execution with unknown error."""
59+
# Setup
60+
mock_stream_manager.call_tool.return_value = {
61+
"isError": True
62+
}
63+
64+
# Execute and verify default error message
65+
with pytest.raises(RuntimeError, match="Unknown error"):
66+
await mcp_tool.execute(message="Hello")
67+
68+
@pytest.mark.asyncio
69+
async def test_execute_transport_error(self, mcp_tool, mock_stream_manager):
70+
"""Test tool call with transport error."""
71+
# Simulate transport error
72+
mock_stream_manager.call_tool.side_effect = Exception("Transport error")
73+
74+
# Should catch and reraise as RuntimeError
75+
with pytest.raises(Exception, match="Transport error"):
76+
await mcp_tool.execute(message="test")
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
# tests/mcp/test_register_mcp_tools.py
2+
import pytest
3+
from unittest.mock import Mock, patch
4+
5+
from chuk_tool_processor.mcp.register_mcp_tools import register_mcp_tools
6+
from chuk_tool_processor.mcp.stream_manager import StreamManager
7+
from chuk_tool_processor.registry.interface import ToolRegistryInterface
8+
9+
10+
class TestRegisterMCPTools:
11+
"""Test MCP tools registration."""
12+
13+
@pytest.fixture
14+
def mock_registry(self):
15+
"""Mock tool registry."""
16+
mock = Mock(spec=ToolRegistryInterface)
17+
mock.register_tool = Mock()
18+
return mock
19+
20+
@pytest.fixture
21+
def mock_stream_manager(self):
22+
"""Mock stream manager."""
23+
mock = Mock(spec=StreamManager)
24+
tools = [
25+
{"name": "echo", "description": "Echo tool", "inputSchema": {}},
26+
{"name": "calc", "description": "Calculator", "inputSchema": {}}
27+
]
28+
mock.get_all_tools = Mock(return_value=tools)
29+
return mock
30+
31+
def test_register_tools(self, mock_registry, mock_stream_manager):
32+
"""Test registering MCP tools."""
33+
# Mock the registry provider
34+
with patch('chuk_tool_processor.mcp.register_mcp_tools.ToolRegistryProvider.get_registry', return_value=mock_registry):
35+
registered = register_mcp_tools(mock_stream_manager, namespace="mcp")
36+
37+
# Check registration calls
38+
assert len(registered) == 2
39+
assert "echo" in registered
40+
assert "calc" in registered
41+
42+
# Verify register_tool was called with correct args
43+
assert mock_registry.register_tool.call_count == 4 # 2 tools x 2 registrations each
44+
45+
# Check that tools were registered in both namespaces
46+
call_args = mock_registry.register_tool.call_args_list
47+
48+
# Check tools are registered as both "tool" and "namespace.tool"
49+
tool_names = []
50+
for call in call_args:
51+
# Extract name parameter from the call
52+
args, kwargs = call
53+
# The name is the second positional argument
54+
# or in kwargs with key 'name'
55+
if 'name' in kwargs:
56+
tool_names.append(kwargs['name'])
57+
elif len(args) > 1:
58+
tool_names.append(args[1])
59+
60+
assert "echo" in tool_names
61+
assert "mcp.echo" in tool_names
62+
assert "calc" in tool_names
63+
assert "mcp.calc" in tool_names
64+
65+
def test_register_with_invalid_tool(self, mock_registry, mock_stream_manager):
66+
"""Test handling invalid tool definitions."""
67+
tools = [
68+
{"description": "No name"}, # Missing name
69+
{"name": "", "description": "Empty name"} # Empty name
70+
]
71+
mock_stream_manager.get_all_tools = Mock(return_value=tools)
72+
73+
with patch('chuk_tool_processor.mcp.register_mcp_tools.ToolRegistryProvider.get_registry', return_value=mock_registry):
74+
registered = register_mcp_tools(mock_stream_manager)
75+
76+
assert len(registered) == 0
77+
assert mock_registry.register_tool.call_count == 0
78+
79+
def test_empty_tools_registration(self, mock_registry):
80+
"""Test registering with no tools."""
81+
mock_stream_manager = Mock(spec=StreamManager)
82+
mock_stream_manager.get_all_tools = Mock(return_value=[])
83+
84+
with patch('chuk_tool_processor.mcp.register_mcp_tools.ToolRegistryProvider.get_registry', return_value=mock_registry):
85+
registered = register_mcp_tools(mock_stream_manager)
86+
87+
assert len(registered) == 0
88+
assert mock_registry.register_tool.call_count == 0
89+
90+
def test_duplicate_tool_names(self, mock_registry):
91+
"""Test registering tools with duplicate names."""
92+
mock_stream_manager = Mock(spec=StreamManager)
93+
tools = [
94+
{"name": "echo", "description": "First echo"},
95+
{"name": "echo", "description": "Second echo"} # Duplicate
96+
]
97+
mock_stream_manager.get_all_tools = Mock(return_value=tools)
98+
99+
with patch('chuk_tool_processor.mcp.register_mcp_tools.ToolRegistryProvider.get_registry', return_value=mock_registry):
100+
registered = register_mcp_tools(mock_stream_manager)
101+
102+
# Should register both (last one overwrites)
103+
assert len(registered) == 2 # Count includes duplicates
104+
# Each tool is registered twice (once in mcp namespace, once in default namespace)
105+
# But due to duplicates, we expect 4 calls total (2 tools x 2 namespaces)
106+
assert mock_registry.register_tool.call_count == 4 # 2 tools x 2 namespaces

tests/mcp/test_setup_mcp_sse.py

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
# tests/mcp/test_setup_mcp_sse.py
2+
import pytest
3+
from unittest.mock import Mock, AsyncMock, patch
4+
5+
from chuk_tool_processor.mcp.setup_mcp_sse import setup_mcp_sse
6+
from chuk_tool_processor.core.processor import ToolProcessor
7+
from chuk_tool_processor.mcp.stream_manager import StreamManager
8+
9+
10+
class TestSetupMCPSSE:
11+
"""Test MCP SSE setup function."""
12+
13+
@pytest.mark.asyncio
14+
async def test_setup_mcp_sse(self):
15+
"""Test complete MCP SSE setup."""
16+
# Mock dependencies
17+
mock_stream_manager = Mock(spec=StreamManager)
18+
mock_processor = Mock(spec=ToolProcessor)
19+
registered_tools = ["weather", "geocoding"]
20+
21+
with patch('chuk_tool_processor.mcp.setup_mcp_sse.StreamManager.create_with_sse',
22+
AsyncMock(return_value=mock_stream_manager)):
23+
with patch('chuk_tool_processor.mcp.setup_mcp_sse.register_mcp_tools',
24+
return_value=registered_tools):
25+
with patch('chuk_tool_processor.mcp.setup_mcp_sse.ToolProcessor',
26+
return_value=mock_processor):
27+
28+
servers = [
29+
{"name": "weather", "url": "http://test.com"},
30+
{"name": "geocoding", "url": "http://geo.com"}
31+
]
32+
33+
processor, stream_manager = await setup_mcp_sse(
34+
servers=servers,
35+
namespace="remote",
36+
enable_caching=True
37+
)
38+
39+
assert processor == mock_processor
40+
assert stream_manager == mock_stream_manager
41+
42+
@pytest.mark.asyncio
43+
async def test_setup_with_custom_options(self):
44+
"""Test setup with custom configuration options."""
45+
with patch('chuk_tool_processor.mcp.setup_mcp_sse.StreamManager.create_with_sse',
46+
AsyncMock()):
47+
with patch('chuk_tool_processor.mcp.setup_mcp_sse.register_mcp_tools'):
48+
with patch('chuk_tool_processor.mcp.setup_mcp_sse.ToolProcessor') as mock_processor_class:
49+
50+
servers = [{"name": "test", "url": "http://test.com"}]
51+
52+
await setup_mcp_sse(
53+
servers=servers,
54+
default_timeout=45.0,
55+
max_concurrency=10,
56+
cache_ttl=900,
57+
enable_rate_limiting=True,
58+
max_retries=10
59+
)
60+
61+
# Verify ToolProcessor was created with correct options
62+
mock_processor_class.assert_called_once()
63+
call_kwargs = mock_processor_class

tests/mcp/test_setup_mcp_stdio.py

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
# tests/mcp/test_setup_mcp_stdio.py
2+
import pytest
3+
from unittest.mock import Mock, AsyncMock, patch
4+
5+
from chuk_tool_processor.mcp.setup_mcp_stdio import setup_mcp_stdio
6+
from chuk_tool_processor.core.processor import ToolProcessor
7+
from chuk_tool_processor.mcp.stream_manager import StreamManager
8+
9+
10+
class TestSetupMCPStdio:
11+
"""Test MCP stdio setup function."""
12+
13+
@pytest.mark.asyncio
14+
async def test_setup_mcp_stdio(self):
15+
"""Test complete MCP stdio setup."""
16+
# Mock dependencies
17+
mock_stream_manager = Mock(spec=StreamManager)
18+
mock_processor = Mock(spec=ToolProcessor)
19+
registered_tools = ["echo", "calc"]
20+
21+
with patch('chuk_tool_processor.mcp.setup_mcp_stdio.StreamManager.create',
22+
AsyncMock(return_value=mock_stream_manager)):
23+
with patch('chuk_tool_processor.mcp.setup_mcp_stdio.register_mcp_tools',
24+
return_value=registered_tools):
25+
with patch('chuk_tool_processor.mcp.setup_mcp_stdio.ToolProcessor',
26+
return_value=mock_processor):
27+
28+
processor, stream_manager = await setup_mcp_stdio(
29+
config_file="test.json",
30+
servers=["echo"],
31+
namespace="mcp",
32+
enable_caching=True,
33+
enable_retries=True
34+
)
35+
36+
assert processor == mock_processor
37+
assert stream_manager == mock_stream_manager
38+
39+
@pytest.mark.asyncio
40+
async def test_setup_with_custom_options(self):
41+
"""Test setup with custom configuration options."""
42+
with patch('chuk_tool_processor.mcp.setup_mcp_stdio.StreamManager.create',
43+
AsyncMock()):
44+
with patch('chuk_tool_processor.mcp.setup_mcp_stdio.register_mcp_tools'):
45+
with patch('chuk_tool_processor.mcp.setup_mcp_stdio.ToolProcessor') as mock_processor_class:
46+
47+
await setup_mcp_stdio(
48+
config_file="test.json",
49+
servers=["echo"],
50+
default_timeout=30.0,
51+
max_concurrency=5,
52+
cache_ttl=600,
53+
enable_rate_limiting=True,
54+
global_rate_limit=100,
55+
max_retries=5
56+
)
57+
58+
# Verify ToolProcessor was created with correct options
59+
mock_processor_class.assert_called_once()
60+
call_kwargs = mock_processor_class.call_args.kwargs
61+
assert call_kwargs['default_timeout'] == 30.0
62+
assert call_kwargs['max_concurrency'] == 5
63+
assert call_kwargs['cache_ttl'] == 600
64+
assert call_kwargs['enable_rate_limiting'] is True
65+
assert call_kwargs['global_rate_limit'] == 100
66+
assert call_kwargs['max_retries'] == 5

0 commit comments

Comments
 (0)