507 lines
21 KiB
Python
507 lines
21 KiB
Python
"""Integration tests for MCP (Model Context Protocol) system."""
|
|
|
|
import asyncio
|
|
from unittest.mock import AsyncMock, patch
|
|
|
|
import pytest
|
|
|
|
from cleverclaude.agents.manager import AgentManager
|
|
from cleverclaude.config.settings import Settings
|
|
from cleverclaude.coordination.swarm import SwarmCoordinator
|
|
from cleverclaude.core.app import CleverClaudeApp
|
|
from cleverclaude.mcp.client import MCPClient
|
|
from cleverclaude.mcp.types import MCPToolExecutionResult
|
|
|
|
|
|
@pytest.mark.integration
|
|
@pytest.mark.async_test
|
|
class TestMCPIntegration:
|
|
"""Integration tests for MCP system with other components."""
|
|
|
|
async def test_mcp_with_agent_manager(self, test_settings: Settings, async_session, mock_redis):
|
|
"""Test MCP integration with AgentManager."""
|
|
# Initialize MCP client and agent manager
|
|
mcp_client = MCPClient(test_settings)
|
|
agent_manager = AgentManager(test_settings.agents, async_session, mock_redis)
|
|
|
|
await mcp_client.initialize()
|
|
await agent_manager.initialize()
|
|
|
|
# Mock MCP tool for agent creation
|
|
with patch.object(mcp_client, "execute_tool") as mock_execute:
|
|
mock_execute.return_value = MCPToolExecutionResult(
|
|
success=True, result={"agent_id": "mcp_agent_123", "type": "researcher", "status": "created"}
|
|
)
|
|
|
|
# Execute MCP tool to create agent
|
|
result = await mcp_client.execute_tool(
|
|
"agent_spawn",
|
|
{"type": "researcher", "name": "MCP Test Agent", "capabilities": ["research", "analysis"]},
|
|
)
|
|
|
|
assert result.success is True
|
|
assert result.result["agent_id"] == "mcp_agent_123"
|
|
|
|
await mcp_client.disconnect()
|
|
await agent_manager.shutdown()
|
|
|
|
async def test_mcp_with_swarm_coordinator(self, test_settings: Settings, async_session, mock_redis):
|
|
"""Test MCP integration with SwarmCoordinator."""
|
|
mcp_client = MCPClient(test_settings)
|
|
agent_manager = AgentManager(test_settings.agents, async_session, mock_redis)
|
|
swarm_coordinator = SwarmCoordinator(test_settings.swarm, async_session, agent_manager, mock_redis)
|
|
|
|
await mcp_client.initialize()
|
|
await agent_manager.initialize()
|
|
await swarm_coordinator.initialize()
|
|
|
|
# Test swarm creation via MCP
|
|
with patch.object(mcp_client, "execute_tool") as mock_execute:
|
|
mock_execute.return_value = MCPToolExecutionResult(
|
|
success=True, result={"swarm_id": "mcp_swarm_456", "topology": "mesh", "status": "created"}
|
|
)
|
|
|
|
result = await mcp_client.execute_tool(
|
|
"swarm_init", {"topology": "mesh", "maxAgents": 10, "strategy": "balanced"}
|
|
)
|
|
|
|
assert result.success is True
|
|
assert result.result["swarm_id"] == "mcp_swarm_456"
|
|
|
|
await swarm_coordinator.shutdown()
|
|
await agent_manager.shutdown()
|
|
await mcp_client.disconnect()
|
|
|
|
async def test_mcp_end_to_end_workflow(self, test_settings: Settings, async_session, mock_redis):
|
|
"""Test complete end-to-end workflow using MCP tools."""
|
|
mcp_client = MCPClient(test_settings)
|
|
await mcp_client.initialize()
|
|
|
|
# Mock a complete workflow: swarm -> agents -> tasks -> results
|
|
workflow_steps = [
|
|
("swarm_init", {"topology": "hierarchical"}, {"swarm_id": "workflow_swarm"}),
|
|
("agent_spawn", {"type": "researcher"}, {"agent_id": "workflow_agent_1"}),
|
|
("agent_spawn", {"type": "coder"}, {"agent_id": "workflow_agent_2"}),
|
|
("task_orchestrate", {"task": "Complex analysis task"}, {"task_id": "workflow_task_1"}),
|
|
("task_status", {"taskId": "workflow_task_1"}, {"status": "completed"}),
|
|
("performance_report", {"format": "detailed"}, {"metrics": {"efficiency": 92.5}}),
|
|
]
|
|
|
|
results = []
|
|
with patch.object(mcp_client, "execute_tool") as mock_execute:
|
|
mock_execute.side_effect = [
|
|
MCPToolExecutionResult(success=True, result=expected_result) for _, _, expected_result in workflow_steps
|
|
]
|
|
|
|
for tool_name, params, expected_result in workflow_steps:
|
|
result = await mcp_client.execute_tool(tool_name, params)
|
|
results.append(result)
|
|
|
|
assert result.success is True
|
|
assert result.result == expected_result
|
|
|
|
# Verify workflow completion
|
|
assert len(results) == 6
|
|
assert all(r.success for r in results)
|
|
|
|
await mcp_client.disconnect()
|
|
|
|
async def test_mcp_error_recovery(self, test_settings: Settings):
|
|
"""Test MCP error recovery and resilience."""
|
|
mcp_client = MCPClient(test_settings)
|
|
await mcp_client.initialize()
|
|
|
|
# Test recovery from tool execution errors
|
|
with patch.object(mcp_client, "execute_tool") as mock_execute:
|
|
# First call fails, second succeeds
|
|
mock_execute.side_effect = [
|
|
MCPToolExecutionResult(success=False, result=None, error="Temporary network error"),
|
|
MCPToolExecutionResult(success=True, result={"swarm_id": "recovered_swarm"}),
|
|
]
|
|
|
|
# First attempt should fail
|
|
result1 = await mcp_client.execute_tool("swarm_init", {"topology": "mesh"})
|
|
assert result1.success is False
|
|
assert "network error" in result1.error.lower()
|
|
|
|
# Second attempt should succeed (simulating retry)
|
|
result2 = await mcp_client.execute_tool("swarm_init", {"topology": "mesh"})
|
|
assert result2.success is True
|
|
assert result2.result["swarm_id"] == "recovered_swarm"
|
|
|
|
await mcp_client.disconnect()
|
|
|
|
async def test_mcp_concurrent_operations(self, test_settings: Settings):
|
|
"""Test concurrent MCP operations."""
|
|
mcp_client = MCPClient(test_settings)
|
|
await mcp_client.initialize()
|
|
|
|
# Define concurrent operations
|
|
concurrent_ops = [
|
|
("swarm_status", {}, {"active_swarms": 2}),
|
|
("agent_metrics", {"agentId": "agent_1"}, {"performance": 85.5}),
|
|
("memory_usage", {"action": "list"}, {"total_keys": 42}),
|
|
("neural_status", {"modelId": "model_1"}, {"status": "trained"}),
|
|
("performance_report", {"format": "summary"}, {"uptime": "24h"}),
|
|
]
|
|
|
|
async def execute_operation(tool_name, params, expected_result):
|
|
with patch.object(mcp_client, "execute_tool") as mock_execute:
|
|
mock_execute.return_value = MCPToolExecutionResult(success=True, result=expected_result)
|
|
return await mcp_client.execute_tool(tool_name, params)
|
|
|
|
# Execute operations concurrently
|
|
tasks = [execute_operation(tool_name, params, expected) for tool_name, params, expected in concurrent_ops]
|
|
|
|
results = await asyncio.gather(*tasks)
|
|
|
|
# Verify all operations completed successfully
|
|
assert len(results) == 5
|
|
assert all(r.success for r in results)
|
|
|
|
await mcp_client.disconnect()
|
|
|
|
async def test_mcp_with_full_application(self, test_settings: Settings, temp_dir):
|
|
"""Test MCP integration with full CleverClaude application."""
|
|
# Create config directory
|
|
config_dir = temp_dir / ".cleverclaude"
|
|
config_dir.mkdir(exist_ok=True)
|
|
|
|
with patch("cleverclaude.core.app.CleverClaudeApp._initialize_mcp") as mock_init_mcp:
|
|
mock_mcp_client = AsyncMock()
|
|
mock_mcp_client.get_available_tools.return_value = {
|
|
"swarm_init": {"description": "Initialize swarm"},
|
|
"agent_spawn": {"description": "Spawn agent"},
|
|
"task_orchestrate": {"description": "Orchestrate task"},
|
|
}
|
|
mock_init_mcp.return_value = mock_mcp_client
|
|
|
|
# Initialize application
|
|
app = CleverClaudeApp(config_dir)
|
|
|
|
with (
|
|
patch.object(app, "_initialize_database"),
|
|
patch.object(app, "_initialize_redis"),
|
|
patch.object(app, "_initialize_agents"),
|
|
patch.object(app, "_initialize_swarm"),
|
|
):
|
|
await app.initialize()
|
|
|
|
# Verify MCP client was initialized
|
|
assert app.mcp_client is not None
|
|
mock_init_mcp.assert_called_once()
|
|
|
|
await app.shutdown()
|
|
|
|
|
|
@pytest.mark.integration
|
|
@pytest.mark.async_test
|
|
class TestMCPTools:
|
|
"""Integration tests for specific MCP tools."""
|
|
|
|
async def test_neural_tools_integration(self, test_settings: Settings):
|
|
"""Test neural network tools integration."""
|
|
mcp_client = MCPClient(test_settings)
|
|
await mcp_client.initialize()
|
|
|
|
# Test neural training workflow
|
|
training_workflow = [
|
|
(
|
|
"neural_train",
|
|
{"pattern_type": "coordination", "training_data": "sample_coordination_data", "epochs": 10},
|
|
),
|
|
("neural_status", {"modelId": "coordination_model"}),
|
|
("neural_predict", {"modelId": "coordination_model", "input": "test_coordination_scenario"}),
|
|
]
|
|
|
|
with patch.object(mcp_client, "execute_tool") as mock_execute:
|
|
mock_execute.side_effect = [
|
|
MCPToolExecutionResult(success=True, result={"training_id": "train_123", "status": "started"}),
|
|
MCPToolExecutionResult(success=True, result={"status": "trained", "accuracy": 0.92}),
|
|
MCPToolExecutionResult(success=True, result={"prediction": "optimal_coordination", "confidence": 0.88}),
|
|
]
|
|
|
|
results = []
|
|
for tool_name, params in training_workflow:
|
|
result = await mcp_client.execute_tool(tool_name, params)
|
|
results.append(result)
|
|
|
|
assert len(results) == 3
|
|
assert all(r.success for r in results)
|
|
assert results[0].result["status"] == "started"
|
|
assert results[1].result["accuracy"] == 0.92
|
|
assert results[2].result["confidence"] == 0.88
|
|
|
|
await mcp_client.disconnect()
|
|
|
|
async def test_memory_tools_integration(self, test_settings: Settings):
|
|
"""Test memory management tools integration."""
|
|
mcp_client = MCPClient(test_settings)
|
|
await mcp_client.initialize()
|
|
|
|
# Test memory operations workflow
|
|
memory_ops = [
|
|
(
|
|
"memory_usage",
|
|
{"action": "store", "key": "test_key", "value": "test_value", "namespace": "integration_test"},
|
|
),
|
|
("memory_usage", {"action": "retrieve", "key": "test_key", "namespace": "integration_test"}),
|
|
("memory_search", {"pattern": "test_*", "namespace": "integration_test"}),
|
|
("memory_usage", {"action": "delete", "key": "test_key", "namespace": "integration_test"}),
|
|
]
|
|
|
|
with patch.object(mcp_client, "execute_tool") as mock_execute:
|
|
mock_execute.side_effect = [
|
|
MCPToolExecutionResult(success=True, result={"action": "store", "status": "success"}),
|
|
MCPToolExecutionResult(success=True, result={"value": "test_value", "found": True}),
|
|
MCPToolExecutionResult(success=True, result={"matches": ["test_key"], "count": 1}),
|
|
MCPToolExecutionResult(success=True, result={"action": "delete", "status": "success"}),
|
|
]
|
|
|
|
results = []
|
|
for tool_name, params in memory_ops:
|
|
result = await mcp_client.execute_tool(tool_name, params)
|
|
results.append(result)
|
|
|
|
assert len(results) == 4
|
|
assert all(r.success for r in results)
|
|
assert results[1].result["value"] == "test_value"
|
|
assert results[2].result["count"] == 1
|
|
|
|
await mcp_client.disconnect()
|
|
|
|
async def test_workflow_tools_integration(self, test_settings: Settings):
|
|
"""Test workflow automation tools integration."""
|
|
mcp_client = MCPClient(test_settings)
|
|
await mcp_client.initialize()
|
|
|
|
# Test workflow creation and execution
|
|
workflow_definition = {
|
|
"name": "Integration Test Workflow",
|
|
"steps": [
|
|
{"action": "create_agents", "count": 3},
|
|
{"action": "create_swarm", "topology": "mesh"},
|
|
{"action": "assign_tasks", "task_count": 5},
|
|
{"action": "monitor_execution"},
|
|
{"action": "collect_results"},
|
|
],
|
|
"triggers": ["on_demand"],
|
|
}
|
|
|
|
workflow_ops = [
|
|
("workflow_create", workflow_definition),
|
|
("workflow_execute", {"workflowId": "workflow_123"}),
|
|
("workflow_status", {"workflowId": "workflow_123"}),
|
|
("workflow_results", {"workflowId": "workflow_123"}),
|
|
]
|
|
|
|
with patch.object(mcp_client, "execute_tool") as mock_execute:
|
|
mock_execute.side_effect = [
|
|
MCPToolExecutionResult(success=True, result={"workflow_id": "workflow_123", "status": "created"}),
|
|
MCPToolExecutionResult(success=True, result={"execution_id": "exec_456", "status": "running"}),
|
|
MCPToolExecutionResult(success=True, result={"status": "completed", "progress": 100}),
|
|
MCPToolExecutionResult(success=True, result={"results": {"tasks_completed": 5, "success_rate": 100}}),
|
|
]
|
|
|
|
results = []
|
|
for tool_name, params in workflow_ops:
|
|
result = await mcp_client.execute_tool(tool_name, params)
|
|
results.append(result)
|
|
|
|
assert len(results) == 4
|
|
assert all(r.success for r in results)
|
|
assert results[0].result["workflow_id"] == "workflow_123"
|
|
assert results[2].result["progress"] == 100
|
|
assert results[3].result["results"]["success_rate"] == 100
|
|
|
|
await mcp_client.disconnect()
|
|
|
|
|
|
@pytest.mark.integration
|
|
@pytest.mark.slow
|
|
class TestMCPPerformance:
|
|
"""Performance and stress tests for MCP system."""
|
|
|
|
@pytest.mark.async_test
|
|
async def test_mcp_high_throughput(self, test_settings: Settings):
|
|
"""Test MCP system under high throughput."""
|
|
mcp_client = MCPClient(test_settings)
|
|
await mcp_client.initialize()
|
|
|
|
# Execute many operations rapidly
|
|
num_operations = 100
|
|
operations = []
|
|
|
|
for _i in range(num_operations):
|
|
operations.append(("swarm_status", {"detailed": False}))
|
|
|
|
with patch.object(mcp_client, "execute_tool") as mock_execute:
|
|
mock_execute.return_value = MCPToolExecutionResult(success=True, result={"status": "running", "swarms": 2})
|
|
|
|
start_time = asyncio.get_event_loop().time()
|
|
|
|
# Execute operations in batches to avoid overwhelming
|
|
batch_size = 20
|
|
results = []
|
|
for i in range(0, num_operations, batch_size):
|
|
batch = operations[i : i + batch_size]
|
|
batch_tasks = [mcp_client.execute_tool(tool_name, params) for tool_name, params in batch]
|
|
batch_results = await asyncio.gather(*batch_tasks)
|
|
results.extend(batch_results)
|
|
|
|
end_time = asyncio.get_event_loop().time()
|
|
execution_time = end_time - start_time
|
|
|
|
# Verify performance
|
|
assert len(results) == num_operations
|
|
assert all(r.success for r in results)
|
|
assert execution_time < 10.0 # Should complete within 10 seconds
|
|
|
|
throughput = num_operations / execution_time
|
|
assert throughput > 10 # Should handle more than 10 ops/second
|
|
|
|
await mcp_client.disconnect()
|
|
|
|
@pytest.mark.async_test
|
|
async def test_mcp_connection_resilience(self, test_settings: Settings):
|
|
"""Test MCP connection resilience under stress."""
|
|
mcp_client = MCPClient(test_settings)
|
|
await mcp_client.initialize()
|
|
|
|
# Simulate connection failures and recoveries
|
|
failure_count = 0
|
|
success_count = 0
|
|
|
|
def mock_execute_with_failures(tool_name, params):
|
|
nonlocal failure_count, success_count
|
|
|
|
# Simulate intermittent failures (20% failure rate)
|
|
if (success_count + failure_count) % 5 == 0:
|
|
failure_count += 1
|
|
return MCPToolExecutionResult(success=False, result=None, error="Connection temporarily unavailable")
|
|
else:
|
|
success_count += 1
|
|
return MCPToolExecutionResult(success=True, result={"status": "success", "operation": tool_name})
|
|
|
|
with patch.object(mcp_client, "execute_tool", side_effect=mock_execute_with_failures):
|
|
# Execute operations with expected failures
|
|
num_operations = 50
|
|
results = []
|
|
|
|
for _i in range(num_operations):
|
|
result = await mcp_client.execute_tool("health_check", {})
|
|
results.append(result)
|
|
|
|
# Verify resilience
|
|
total_results = len(results)
|
|
successful_results = len([r for r in results if r.success])
|
|
failed_results = len([r for r in results if not r.success])
|
|
|
|
assert total_results == num_operations
|
|
assert successful_results > 0 # Should have some successes
|
|
assert failed_results > 0 # Should have some expected failures
|
|
assert successful_results >= failed_results # More successes than failures
|
|
|
|
await mcp_client.disconnect()
|
|
|
|
@pytest.mark.async_test
|
|
async def test_mcp_memory_efficiency(self, test_settings: Settings):
|
|
"""Test MCP memory efficiency during extended operations."""
|
|
mcp_client = MCPClient(test_settings)
|
|
await mcp_client.initialize()
|
|
|
|
# Execute long-running sequence of operations
|
|
import gc
|
|
|
|
initial_objects = len(gc.get_objects())
|
|
|
|
with patch.object(mcp_client, "execute_tool") as mock_execute:
|
|
mock_execute.return_value = MCPToolExecutionResult(
|
|
success=True,
|
|
result={"data": "test" * 100}, # Some data payload
|
|
)
|
|
|
|
# Execute many operations
|
|
for i in range(200):
|
|
result = await mcp_client.execute_tool("memory_usage", {"action": "list"})
|
|
assert result.success
|
|
|
|
# Force garbage collection periodically
|
|
if i % 50 == 0:
|
|
gc.collect()
|
|
|
|
# Final garbage collection
|
|
gc.collect()
|
|
|
|
final_objects = len(gc.get_objects())
|
|
|
|
# Verify no significant memory growth
|
|
object_growth = final_objects - initial_objects
|
|
assert object_growth < 1000 # Should not have excessive object growth
|
|
|
|
await mcp_client.disconnect()
|
|
|
|
|
|
@pytest.mark.integration
|
|
class TestMCPToolValidation:
|
|
"""Test MCP tool parameter validation and error handling."""
|
|
|
|
@pytest.mark.async_test
|
|
async def test_tool_parameter_validation(self, test_settings: Settings):
|
|
"""Test comprehensive tool parameter validation."""
|
|
mcp_client = MCPClient(test_settings)
|
|
await mcp_client.initialize()
|
|
|
|
# Test various invalid parameter scenarios
|
|
invalid_scenarios = [
|
|
("swarm_init", {"topology": "invalid_topology"}, "Invalid topology"),
|
|
("agent_spawn", {"type": "invalid_type"}, "Invalid agent type"),
|
|
("task_orchestrate", {"priority": "invalid_priority"}, "Invalid priority"),
|
|
("memory_usage", {"action": "invalid_action"}, "Invalid action"),
|
|
("neural_train", {"epochs": -1}, "Invalid epochs"),
|
|
]
|
|
|
|
with patch.object(mcp_client, "execute_tool") as mock_execute:
|
|
for tool_name, invalid_params, expected_error in invalid_scenarios:
|
|
mock_execute.return_value = MCPToolExecutionResult(success=False, result=None, error=expected_error)
|
|
|
|
result = await mcp_client.execute_tool(tool_name, invalid_params)
|
|
|
|
assert result.success is False
|
|
assert expected_error.lower() in result.error.lower()
|
|
|
|
await mcp_client.disconnect()
|
|
|
|
@pytest.mark.async_test
|
|
async def test_tool_response_validation(self, test_settings: Settings):
|
|
"""Test MCP tool response validation."""
|
|
mcp_client = MCPClient(test_settings)
|
|
await mcp_client.initialize()
|
|
|
|
# Test various response formats
|
|
response_scenarios = [
|
|
("swarm_init", {"swarm_id": "test", "topology": "mesh", "status": "created"}),
|
|
("agent_spawn", {"agent_id": "test", "type": "researcher", "status": "active"}),
|
|
("task_status", {"task_id": "test", "status": "completed", "progress": 100}),
|
|
("performance_report", {"metrics": {"cpu": 50, "memory": 200}, "timestamp": "2024-01-01T12:00:00Z"}),
|
|
]
|
|
|
|
with patch.object(mcp_client, "execute_tool") as mock_execute:
|
|
for tool_name, expected_response in response_scenarios:
|
|
mock_execute.return_value = MCPToolExecutionResult(success=True, result=expected_response)
|
|
|
|
result = await mcp_client.execute_tool(tool_name, {})
|
|
|
|
assert result.success is True
|
|
assert result.result == expected_response
|
|
|
|
# Verify required fields are present
|
|
if tool_name == "swarm_init":
|
|
assert "swarm_id" in result.result
|
|
assert "topology" in result.result
|
|
elif tool_name == "agent_spawn":
|
|
assert "agent_id" in result.result
|
|
assert "type" in result.result
|
|
|
|
await mcp_client.disconnect()
|