ClaudeFlow ported, needs cleanup

This commit is contained in:
Your Name
2025-08-10 12:00:13 -04:00
parent 08df0d26aa
commit 475b483850
61 changed files with 14025 additions and 3000 deletions
+1
View File
@@ -0,0 +1 @@
"""Test suite for CleverClaude."""
+190
View File
@@ -0,0 +1,190 @@
"""Pytest configuration and shared fixtures for CleverClaude tests."""
import asyncio
import tempfile
from collections.abc import AsyncGenerator, Generator
from pathlib import Path
from unittest.mock import AsyncMock, MagicMock
import pytest
import pytest_asyncio
from sqlalchemy.ext.asyncio import AsyncSession, create_async_engine
from sqlalchemy.pool import StaticPool
from cleverclaude.config.settings import Settings
from cleverclaude.database.models import Base
@pytest.fixture(scope="session")
def event_loop():
"""Create an instance of the default event loop for the test session."""
loop = asyncio.new_event_loop()
yield loop
loop.close()
@pytest.fixture
def temp_dir() -> Generator[Path]:
"""Create a temporary directory for test files."""
with tempfile.TemporaryDirectory(prefix="cleverclaude_test_") as temp_path:
yield Path(temp_path)
@pytest.fixture
def test_settings(temp_dir: Path) -> Settings:
"""Create test settings with temporary directories."""
config_dir = temp_dir / ".cleverclaude"
config_dir.mkdir(exist_ok=True)
return Settings(
app=Settings.AppConfig(name="CleverClaude Test", version="2.0.0-test", environment="testing", debug=True),
database=Settings.DatabaseConfig(url=f"sqlite+aiosqlite:///{config_dir}/test.db", echo=False),
redis=Settings.RedisConfig(
url="redis://localhost:6379/15" # Test database
),
agents=Settings.AgentsConfig(max_agents=10, default_timeout=30, health_check_interval=5),
swarm=Settings.SwarmConfig(default_topology="mesh", max_swarm_size=5, coordination_timeout=30),
api=Settings.APIConfig(
host="127.0.0.1",
port=8080, # Different port for testing
docs_enabled=False,
),
monitoring=Settings.MonitoringConfig(metrics_enabled=False, log_level="DEBUG", log_format="json"),
)
@pytest_asyncio.fixture
async def async_engine(test_settings: Settings):
"""Create async SQLAlchemy engine for testing."""
engine = create_async_engine(
test_settings.database.url,
echo=test_settings.database.echo,
connect_args={"check_same_thread": False},
poolclass=StaticPool,
)
# Create all tables
async with engine.begin() as conn:
await conn.run_sync(Base.metadata.create_all)
yield engine
# Clean up
await engine.dispose()
@pytest_asyncio.fixture
async def async_session(async_engine) -> AsyncGenerator[AsyncSession]:
"""Create async database session for testing."""
async with AsyncSession(async_engine, expire_on_commit=False) as session:
yield session
@pytest.fixture
def mock_redis():
"""Create a mock Redis client."""
mock = MagicMock()
mock.get = AsyncMock()
mock.set = AsyncMock()
mock.delete = AsyncMock()
mock.exists = AsyncMock()
mock.keys = AsyncMock()
mock.expire = AsyncMock()
mock.ping = AsyncMock(return_value=True)
return mock
@pytest.fixture
def mock_agent():
"""Create a mock agent for testing."""
mock = AsyncMock()
mock.agent_id = "test_agent_123"
mock.name = "Test Agent"
mock.agent_type = "researcher"
mock.status = "active"
mock.capabilities = {"research", "analysis"}
mock.created_at = "2024-01-01T12:00:00Z"
mock.health_status = {"cpu_usage": 25.5, "memory_usage": 150.2, "task_count": 3, "status": "healthy"}
return mock
@pytest.fixture
def mock_swarm():
"""Create a mock swarm for testing."""
mock = AsyncMock()
mock.swarm_id = "test_swarm_456"
mock.name = "Test Swarm"
mock.topology = "mesh"
mock.state = "active"
mock.agents = []
mock.tasks = []
mock.created_at = "2024-01-01T12:00:00Z"
mock.metrics = {"total_agents": 3, "active_agents": 2, "completed_tasks": 15, "efficiency_score": 85.5}
return mock
@pytest.fixture
def mock_mcp_client():
"""Create a mock MCP client for testing."""
mock = AsyncMock()
mock.is_connected = True
mock.available_tools = [
"swarm_init",
"agent_spawn",
"task_orchestrate",
"memory_usage",
"neural_train",
"performance_report",
]
mock.execute_tool = AsyncMock()
mock.get_tool_info = AsyncMock()
mock.connect = AsyncMock()
mock.disconnect = AsyncMock()
return mock
@pytest.fixture
def mock_task():
"""Create a mock task for testing."""
return {
"task_id": "test_task_789",
"type": "research_query",
"status": "pending",
"priority": "medium",
"data": {"query": "Test research query", "scope": "general", "depth": "standard"},
"created_at": "2024-01-01T12:00:00Z",
"assigned_agent": None,
"result": None,
}
@pytest.fixture(autouse=True)
def setup_test_environment(test_settings: Settings, monkeypatch):
"""Set up test environment variables."""
monkeypatch.setenv("CLEVERCLAUDE_ENVIRONMENT", "testing")
monkeypatch.setenv("CLEVERCLAUDE_DEBUG", "true")
monkeypatch.setenv("CLEVERCLAUDE_CONFIG_DIR", str(test_settings.database.url.split("///")[1].rsplit("/", 1)[0]))
# Override settings
monkeypatch.setattr("cleverclaude.config.settings.get_settings", lambda: test_settings)
@pytest.fixture
def cleanup_tasks():
"""Cleanup any remaining asyncio tasks after tests."""
yield
# Cancel any remaining tasks
tasks = [task for task in asyncio.all_tasks() if not task.done()]
for task in tasks:
task.cancel()
if tasks:
asyncio.gather(*tasks, return_exceptions=True)
# Marker definitions
pytest.mark.unit = pytest.mark.unit
pytest.mark.integration = pytest.mark.integration
pytest.mark.async_test = pytest.mark.asyncio
pytest.mark.slow = pytest.mark.slow
+1
View File
@@ -0,0 +1 @@
"""Integration tests for CleverClaude components."""
+506
View File
@@ -0,0 +1,506 @@
"""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()
+1
View File
@@ -0,0 +1 @@
"""Unit tests for CleverClaude modules."""
+383
View File
@@ -0,0 +1,383 @@
"""Unit tests for CleverClaude agent management."""
from unittest.mock import AsyncMock, patch
import pytest
from cleverclaude.agents.agent import BaseAgent
from cleverclaude.agents.manager import AgentManager
from cleverclaude.agents.types import AgentConfig, AgentStatus, AgentType
from cleverclaude.config.settings import Settings
@pytest.mark.unit
@pytest.mark.async_test
class TestAgentManager:
"""Test suite for AgentManager class."""
async def test_initialization(self, test_settings: Settings, async_session, mock_redis):
"""Test AgentManager initialization."""
manager = AgentManager(test_settings.agents, async_session, mock_redis)
assert manager.config == test_settings.agents
assert manager.session == async_session
assert manager.redis == mock_redis
assert manager.agents == {}
assert manager._initialized is False
async def test_initialize_manager(self, test_settings: Settings, async_session, mock_redis):
"""Test manager initialization process."""
manager = AgentManager(test_settings.agents, async_session, mock_redis)
with patch("cleverclaude.agents.manager.structlog.get_logger") as mock_logger:
await manager.initialize()
assert manager._initialized is True
mock_logger.assert_called_once()
async def test_create_agent_success(self, test_settings: Settings, async_session, mock_redis):
"""Test successful agent creation."""
manager = AgentManager(test_settings.agents, async_session, mock_redis)
await manager.initialize()
with patch("cleverclaude.agents.factory.AgentFactory.create_agent") as mock_factory:
mock_agent = AsyncMock()
mock_agent.agent_id = "test_agent_123"
mock_agent.name = "Test Agent"
mock_agent.agent_type = AgentType.RESEARCHER
mock_agent.status = AgentStatus.ACTIVE
mock_factory.return_value = mock_agent
agent_id = await manager.create_agent(
agent_type=AgentType.RESEARCHER, name="Test Agent", capabilities={"research", "analysis"}
)
assert agent_id == "test_agent_123"
assert agent_id in manager.agents
mock_factory.assert_called_once()
async def test_create_agent_max_limit(self, test_settings: Settings, async_session, mock_redis):
"""Test agent creation with max limit exceeded."""
# Set very low limit for testing
test_settings.agents.max_agents = 1
manager = AgentManager(test_settings.agents, async_session, mock_redis)
await manager.initialize()
# Create first agent (should succeed)
with patch("cleverclaude.agents.factory.AgentFactory.create_agent") as mock_factory:
mock_agent = AsyncMock()
mock_agent.agent_id = "test_agent_1"
mock_factory.return_value = mock_agent
agent_id_1 = await manager.create_agent(AgentType.RESEARCHER, "Agent 1")
assert agent_id_1 == "test_agent_1"
# Try to create second agent (should fail)
with pytest.raises(ValueError, match="Maximum number of agents reached"):
await manager.create_agent(AgentType.RESEARCHER, "Agent 2")
async def test_get_agent_status(self, test_settings: Settings, async_session, mock_redis, mock_agent):
"""Test getting agent status."""
manager = AgentManager(test_settings.agents, async_session, mock_redis)
await manager.initialize()
# Add mock agent to manager
manager.agents[mock_agent.agent_id] = mock_agent
status = await manager.get_agent_status(mock_agent.agent_id)
assert status["agent_id"] == mock_agent.agent_id
assert status["name"] == mock_agent.name
assert status["type"] == mock_agent.agent_type
assert status["status"] == mock_agent.status
async def test_get_agent_status_not_found(self, test_settings: Settings, async_session, mock_redis):
"""Test getting status for non-existent agent."""
manager = AgentManager(test_settings.agents, async_session, mock_redis)
await manager.initialize()
with pytest.raises(ValueError, match="Agent not found"):
await manager.get_agent_status("non_existent_agent")
async def test_list_agents(self, test_settings: Settings, async_session, mock_redis):
"""Test listing all agents."""
manager = AgentManager(test_settings.agents, async_session, mock_redis)
await manager.initialize()
# Add multiple mock agents
mock_agents = []
for i in range(3):
mock_agent = AsyncMock()
mock_agent.agent_id = f"agent_{i}"
mock_agent.name = f"Agent {i}"
mock_agent.agent_type = AgentType.RESEARCHER
mock_agent.status = AgentStatus.ACTIVE
mock_agents.append(mock_agent)
manager.agents[mock_agent.agent_id] = mock_agent
agents_list = await manager.list_agents()
assert len(agents_list) == 3
for i, agent_info in enumerate(agents_list):
assert agent_info["agent_id"] == f"agent_{i}"
assert agent_info["name"] == f"Agent {i}"
async def test_execute_task(self, test_settings: Settings, async_session, mock_redis, mock_agent, mock_task):
"""Test task execution on agent."""
manager = AgentManager(test_settings.agents, async_session, mock_redis)
await manager.initialize()
# Set up mock agent
manager.agents[mock_agent.agent_id] = mock_agent
mock_agent.execute_task.return_value = {"status": "completed", "result": "task completed"}
result = await manager.execute_task(mock_task, agent_id=mock_agent.agent_id)
assert result["status"] == "completed"
mock_agent.execute_task.assert_called_once_with(mock_task)
async def test_execute_task_auto_assign(self, test_settings: Settings, async_session, mock_redis, mock_task):
"""Test task execution with automatic agent assignment."""
manager = AgentManager(test_settings.agents, async_session, mock_redis)
await manager.initialize()
# Add multiple mock agents
for i in range(2):
mock_agent = AsyncMock()
mock_agent.agent_id = f"agent_{i}"
mock_agent.agent_type = AgentType.RESEARCHER
mock_agent.status = AgentStatus.ACTIVE
mock_agent.capabilities = {"research", "analysis"}
mock_agent.execute_task.return_value = {"status": "completed"}
manager.agents[mock_agent.agent_id] = mock_agent
with patch.object(manager, "_find_suitable_agent") as mock_find:
mock_find.return_value = "agent_0"
result = await manager.execute_task(mock_task)
assert result["status"] == "completed"
mock_find.assert_called_once_with(mock_task)
async def test_destroy_agent(self, test_settings: Settings, async_session, mock_redis, mock_agent):
"""Test agent destruction."""
manager = AgentManager(test_settings.agents, async_session, mock_redis)
await manager.initialize()
# Add mock agent
manager.agents[mock_agent.agent_id] = mock_agent
mock_agent.shutdown = AsyncMock()
await manager.destroy_agent(mock_agent.agent_id)
assert mock_agent.agent_id not in manager.agents
mock_agent.shutdown.assert_called_once()
async def test_health_check(self, test_settings: Settings, async_session, mock_redis, mock_agent):
"""Test agent health check."""
manager = AgentManager(test_settings.agents, async_session, mock_redis)
await manager.initialize()
# Add mock agent
manager.agents[mock_agent.agent_id] = mock_agent
mock_agent.get_health_status.return_value = mock_agent.health_status
health_report = await manager.health_check()
assert len(health_report["agents"]) == 1
assert health_report["agents"][0]["agent_id"] == mock_agent.agent_id
assert health_report["total_agents"] == 1
assert health_report["healthy_agents"] == 1
async def test_shutdown(self, test_settings: Settings, async_session, mock_redis):
"""Test manager shutdown."""
manager = AgentManager(test_settings.agents, async_session, mock_redis)
await manager.initialize()
# Add mock agents
mock_agents = []
for i in range(2):
mock_agent = AsyncMock()
mock_agent.agent_id = f"agent_{i}"
mock_agent.shutdown = AsyncMock()
mock_agents.append(mock_agent)
manager.agents[mock_agent.agent_id] = mock_agent
await manager.shutdown()
# Verify all agents were shut down
for mock_agent in mock_agents:
mock_agent.shutdown.assert_called_once()
assert len(manager.agents) == 0
@pytest.mark.unit
@pytest.mark.async_test
class TestBaseAgent:
"""Test suite for BaseAgent class."""
def test_agent_initialization(self):
"""Test agent initialization with config."""
config = AgentConfig(
agent_id="test_agent",
name="Test Agent",
agent_type=AgentType.RESEARCHER,
capabilities={"research", "analysis"},
timeout=300,
)
agent = BaseAgent(config)
assert agent.agent_id == "test_agent"
assert agent.name == "Test Agent"
assert agent.agent_type == AgentType.RESEARCHER
assert agent.capabilities == {"research", "analysis"}
assert agent.status == AgentStatus.INITIALIZING
assert agent.timeout == 300
async def test_agent_startup(self):
"""Test agent startup process."""
config = AgentConfig(agent_id="test_agent", name="Test Agent", agent_type=AgentType.RESEARCHER)
agent = BaseAgent(config)
await agent.startup()
assert agent.status == AgentStatus.ACTIVE
assert agent.created_at is not None
async def test_agent_execute_task(self, mock_task):
"""Test basic task execution."""
config = AgentConfig(
agent_id="test_agent", name="Test Agent", agent_type=AgentType.RESEARCHER, capabilities={"research"}
)
agent = BaseAgent(config)
await agent.startup()
with patch.object(agent, "_process_task") as mock_process:
mock_process.return_value = {"status": "completed", "result": "processed"}
result = await agent.execute_task(mock_task)
assert result["status"] == "completed"
assert agent.task_count == 1
mock_process.assert_called_once_with(mock_task)
async def test_agent_execute_task_timeout(self, mock_task):
"""Test task execution with timeout."""
config = AgentConfig(
agent_id="test_agent",
name="Test Agent",
agent_type=AgentType.RESEARCHER,
timeout=1, # Very short timeout
)
agent = BaseAgent(config)
await agent.startup()
# Mock a slow task
async def slow_task(task):
import asyncio
await asyncio.sleep(2) # Longer than timeout
return {"status": "completed"}
with patch.object(agent, "_process_task", side_effect=slow_task):
result = await agent.execute_task(mock_task)
assert result["status"] == "error"
assert "timeout" in result["error"].lower()
def test_agent_get_health_status(self):
"""Test agent health status reporting."""
config = AgentConfig(agent_id="test_agent", name="Test Agent", agent_type=AgentType.RESEARCHER)
agent = BaseAgent(config)
agent.task_count = 5
agent.error_count = 1
health = agent.get_health_status()
assert health["agent_id"] == "test_agent"
assert health["status"] == AgentStatus.INITIALIZING
assert health["task_count"] == 5
assert health["error_count"] == 1
assert "cpu_usage" in health
assert "memory_usage" in health
async def test_agent_pause_resume(self):
"""Test agent pause and resume functionality."""
config = AgentConfig(agent_id="test_agent", name="Test Agent", agent_type=AgentType.RESEARCHER)
agent = BaseAgent(config)
await agent.startup()
# Test pause
await agent.pause()
assert agent.status == AgentStatus.PAUSED
# Test resume
await agent.resume()
assert agent.status == AgentStatus.ACTIVE
async def test_agent_shutdown(self):
"""Test agent shutdown process."""
config = AgentConfig(agent_id="test_agent", name="Test Agent", agent_type=AgentType.RESEARCHER)
agent = BaseAgent(config)
await agent.startup()
await agent.shutdown()
assert agent.status == AgentStatus.TERMINATED
assert agent.shutdown_at is not None
@pytest.mark.unit
class TestAgentTypes:
"""Test suite for agent type definitions."""
def test_agent_type_enum(self):
"""Test AgentType enumeration."""
assert AgentType.RESEARCHER == "researcher"
assert AgentType.CODER == "coder"
assert AgentType.ANALYST == "analyst"
assert AgentType.COORDINATOR == "coordinator"
assert AgentType.REVIEWER == "reviewer"
assert AgentType.TESTER == "tester"
def test_agent_status_enum(self):
"""Test AgentStatus enumeration."""
assert AgentStatus.INITIALIZING == "initializing"
assert AgentStatus.ACTIVE == "active"
assert AgentStatus.BUSY == "busy"
assert AgentStatus.PAUSED == "paused"
assert AgentStatus.ERROR == "error"
assert AgentStatus.TERMINATED == "terminated"
def test_agent_config_creation(self):
"""Test AgentConfig creation and validation."""
config = AgentConfig(
agent_id="test_agent",
name="Test Agent",
agent_type=AgentType.RESEARCHER,
capabilities={"research", "analysis"},
timeout=300,
max_concurrent_tasks=5,
)
assert config.agent_id == "test_agent"
assert config.name == "Test Agent"
assert config.agent_type == AgentType.RESEARCHER
assert config.capabilities == {"research", "analysis"}
assert config.timeout == 300
assert config.max_concurrent_tasks == 5
def test_agent_config_defaults(self):
"""Test AgentConfig default values."""
config = AgentConfig(agent_id="test_agent", name="Test Agent", agent_type=AgentType.RESEARCHER)
assert config.capabilities == set()
assert config.timeout == 300 # Default timeout
assert config.max_concurrent_tasks == 1 # Default concurrency
+452
View File
@@ -0,0 +1,452 @@
"""Unit tests for CleverClaude CLI interface."""
from pathlib import Path
from unittest.mock import AsyncMock, patch
import pytest
from click.testing import CliRunner
from cleverclaude.cli.commands.init import InitCommand
from cleverclaude.cli.commands.start import StartCommand
from cleverclaude.cli.commands.status import StatusCommand
from cleverclaude.cli.main import app
from cleverclaude.config.settings import Settings
@pytest.mark.unit
class TestCLIMain:
"""Test suite for main CLI application."""
def test_cli_app_creation(self):
"""Test CLI app initialization."""
assert app.name == "cleverclaude"
assert "Advanced AI Agent Orchestration System" in app.help
def test_version_command(self):
"""Test --version command."""
runner = CliRunner()
result = runner.invoke(app, ["--version"])
assert result.exit_code == 0
assert "CleverClaude Python v" in result.output
def test_help_command(self):
"""Test --help command."""
runner = CliRunner()
result = runner.invoke(app, ["--help"])
assert result.exit_code == 0
assert "CleverClaude" in result.output
assert "init" in result.output
assert "start" in result.output
assert "status" in result.output
def test_subcommand_help(self):
"""Test help for subcommands."""
runner = CliRunner()
# Test init command help
result = runner.invoke(app, ["init", "--help"])
assert result.exit_code == 0
assert "Initialize" in result.output
# Test start command help
result = runner.invoke(app, ["start", "--help"])
assert result.exit_code == 0
assert "Start" in result.output
# Test status command help
result = runner.invoke(app, ["status", "--help"])
assert result.exit_code == 0
assert "Display" in result.output
def test_invalid_command(self):
"""Test invalid command handling."""
runner = CliRunner()
result = runner.invoke(app, ["invalid_command"])
assert result.exit_code != 0
assert "No such command" in result.output
@pytest.mark.unit
@pytest.mark.async_test
class TestInitCommand:
"""Test suite for init command."""
async def test_init_command_basic(self, temp_dir: Path, test_settings: Settings):
"""Test basic init command execution."""
import structlog
from rich.console import Console
console = Console()
logger = structlog.get_logger()
init_cmd = InitCommand(console, logger)
await init_cmd.execute(directory=temp_dir, template="default", force=False)
# Verify directory structure was created
assert (temp_dir / ".cleverclaude").exists()
assert (temp_dir / ".cleverclaude" / "config.yaml").exists()
assert (temp_dir / "examples").exists()
assert (temp_dir / ".env.example").exists()
async def test_init_command_with_template(self, temp_dir: Path):
"""Test init command with production template."""
import structlog
from rich.console import Console
console = Console()
logger = structlog.get_logger()
init_cmd = InitCommand(console, logger)
await init_cmd.execute(directory=temp_dir, template="production", force=False)
# Verify production-specific files
assert (temp_dir / "docker-compose.yml").exists()
# Check config contains production settings
config_content = (temp_dir / ".cleverclaude" / "config.yaml").read_text()
assert "production" in config_content
async def test_init_command_force_overwrite(self, temp_dir: Path):
"""Test init command with force overwrite."""
import structlog
from rich.console import Console
console = Console()
logger = structlog.get_logger()
# Create existing files
existing_file = temp_dir / "existing.txt"
existing_file.write_text("existing content")
init_cmd = InitCommand(console, logger)
# Should succeed with force=True
await init_cmd.execute(directory=temp_dir, template="default", force=True)
assert (temp_dir / ".cleverclaude").exists()
async def test_init_command_non_empty_directory_without_force(self, temp_dir: Path):
"""Test init command fails on non-empty directory without force."""
import structlog
from rich.console import Console
console = Console()
logger = structlog.get_logger()
# Create existing file
(temp_dir / "existing.txt").write_text("content")
init_cmd = InitCommand(console, logger)
# Should raise error without force
with pytest.raises(RuntimeError, match="not empty"):
await init_cmd.execute(directory=temp_dir, template="default", force=False)
def test_init_config_templates(self):
"""Test configuration template generation."""
import structlog
from rich.console import Console
console = Console()
logger = structlog.get_logger()
init_cmd = InitCommand(console, logger)
# Test default template
default_config = init_cmd._get_config_template("default")
assert "development" in default_config
assert "debug: true" in default_config
# Test production template
prod_config = init_cmd._get_config_template("production")
assert "production" in prod_config
assert "debug: false" in prod_config
def test_init_example_generation(self):
"""Test example file generation."""
import structlog
from rich.console import Console
console = Console()
logger = structlog.get_logger()
init_cmd = InitCommand(console, logger)
# Test agent example
agent_example = init_cmd._get_agent_example()
assert "AgentManager" in agent_example
assert "create_agent" in agent_example
# Test swarm example
swarm_example = init_cmd._get_swarm_example()
assert "SwarmCoordinator" in swarm_example
assert "add_agent" in swarm_example
# Test task example
task_example = init_cmd._get_task_example()
assert "TaskOrchestrator" in task_example
assert "execute_workflow" in task_example
@pytest.mark.unit
@pytest.mark.async_test
class TestStartCommand:
"""Test suite for start command."""
async def test_start_command_basic(self, test_settings: Settings):
"""Test basic start command execution."""
import structlog
from rich.console import Console
console = Console()
logger = structlog.get_logger()
with patch("cleverclaude.core.app.CleverClaudeApp") as mock_app_class:
mock_app = AsyncMock()
mock_app_class.return_value = mock_app
start_cmd = StartCommand(console, logger)
await start_cmd.execute(config_dir=None, port=8000, host="127.0.0.1", daemon=False)
mock_app.initialize.assert_called_once()
mock_app.start.assert_called_once()
async def test_start_command_with_config_dir(self, temp_dir: Path):
"""Test start command with custom config directory."""
import structlog
from rich.console import Console
console = Console()
logger = structlog.get_logger()
# Create config directory
config_dir = temp_dir / ".cleverclaude"
config_dir.mkdir()
with patch("cleverclaude.core.app.CleverClaudeApp") as mock_app_class:
mock_app = AsyncMock()
mock_app_class.return_value = mock_app
start_cmd = StartCommand(console, logger)
await start_cmd.execute(config_dir=config_dir, port=8080, host="0.0.0.0", daemon=False)
mock_app.initialize.assert_called_once()
# Verify config directory was set
call_args = mock_app_class.call_args
assert call_args is not None
async def test_start_command_daemon_mode(self):
"""Test start command in daemon mode."""
import structlog
from rich.console import Console
console = Console()
logger = structlog.get_logger()
with patch("cleverclaude.core.app.CleverClaudeApp") as mock_app_class:
mock_app = AsyncMock()
mock_app_class.return_value = mock_app
with patch("cleverclaude.cli.commands.start.start_daemon") as mock_daemon:
start_cmd = StartCommand(console, logger)
await start_cmd.execute(config_dir=None, port=8000, host="127.0.0.1", daemon=True)
mock_daemon.assert_called_once()
async def test_start_command_error_handling(self):
"""Test start command error handling."""
import structlog
from rich.console import Console
console = Console()
logger = structlog.get_logger()
with patch("cleverclaude.core.app.CleverClaudeApp") as mock_app_class:
mock_app = AsyncMock()
mock_app.initialize.side_effect = Exception("Initialization failed")
mock_app_class.return_value = mock_app
start_cmd = StartCommand(console, logger)
with pytest.raises(Exception, match="Initialization failed"):
await start_cmd.execute(config_dir=None, port=8000, host="127.0.0.1", daemon=False)
@pytest.mark.unit
@pytest.mark.async_test
class TestStatusCommand:
"""Test suite for status command."""
async def test_status_command_basic(self):
"""Test basic status command execution."""
import structlog
from rich.console import Console
console = Console()
logger = structlog.get_logger()
mock_status = {
"system": {"status": "running", "uptime": "00:15:23", "version": "2.0.0"},
"agents": {"total": 5, "active": 4, "busy": 1},
"swarms": {"total": 2, "active": 2},
"performance": {"cpu_usage": 25.5, "memory_usage": 512.3, "tasks_completed": 142},
}
with patch("cleverclaude.cli.commands.status.get_system_status") as mock_get_status:
mock_get_status.return_value = mock_status
status_cmd = StatusCommand(console, logger)
result = await status_cmd.execute(format="table", watch=False, interval=5)
assert result is not None
mock_get_status.assert_called_once()
async def test_status_command_json_format(self):
"""Test status command with JSON format."""
import structlog
from rich.console import Console
console = Console()
logger = structlog.get_logger()
mock_status = {"system": {"status": "running"}, "agents": {"total": 3}, "swarms": {"total": 1}}
with patch("cleverclaude.cli.commands.status.get_system_status") as mock_get_status:
mock_get_status.return_value = mock_status
status_cmd = StatusCommand(console, logger)
result = await status_cmd.execute(format="json", watch=False, interval=5)
assert result == mock_status
async def test_status_command_watch_mode(self):
"""Test status command in watch mode."""
import structlog
from rich.console import Console
console = Console()
logger = structlog.get_logger()
mock_status = {"system": {"status": "running"}}
call_count = 0
def mock_get_status():
nonlocal call_count
call_count += 1
if call_count >= 3: # Stop after 3 calls
raise KeyboardInterrupt()
return mock_status
with (
patch("cleverclaude.cli.commands.status.get_system_status", side_effect=mock_get_status),
patch("asyncio.sleep") as mock_sleep,
):
status_cmd = StatusCommand(console, logger)
with pytest.raises(KeyboardInterrupt):
await status_cmd.execute(format="table", watch=True, interval=1)
assert call_count == 3
assert mock_sleep.call_count >= 2 # Should have slept between calls
async def test_status_command_service_unavailable(self):
"""Test status command when service is unavailable."""
import structlog
from rich.console import Console
console = Console()
logger = structlog.get_logger()
with patch("cleverclaude.cli.commands.status.get_system_status") as mock_get_status:
mock_get_status.side_effect = ConnectionError("Service unavailable")
status_cmd = StatusCommand(console, logger)
result = await status_cmd.execute(format="table", watch=False, interval=5)
assert result["system"]["status"] == "unavailable"
@pytest.mark.unit
class TestCLIIntegration:
"""Integration tests for CLI commands."""
def test_full_cli_workflow(self, temp_dir):
"""Test full CLI workflow: init -> start -> status."""
runner = CliRunner()
# Test init command
with runner.isolated_filesystem():
result = runner.invoke(app, ["init", "--dir", str(temp_dir), "--template", "default"])
assert result.exit_code == 0
assert "initialized successfully" in result.output
# Mock the start command to avoid actually starting services
with patch("cleverclaude.core.app.CleverClaudeApp"):
result = runner.invoke(app, ["start", "--config-dir", str(temp_dir / ".cleverclaude")])
# This would normally start the app, but we're mocking it
def test_cli_error_handling(self):
"""Test CLI error handling for invalid arguments."""
runner = CliRunner()
# Test init with invalid template
result = runner.invoke(app, ["init", "--template", "invalid_template"])
# Should handle gracefully
# Test start with invalid port
result = runner.invoke(app, ["start", "--port", "invalid_port"])
assert result.exit_code != 0
# Test status with invalid format
result = runner.invoke(app, ["status", "--format", "invalid_format"])
assert result.exit_code != 0
def test_cli_with_environment_variables(self, monkeypatch):
"""Test CLI behavior with environment variables."""
runner = CliRunner()
# Set environment variables
monkeypatch.setenv("CLEVERCLAUDE_API_HOST", "192.168.1.100")
monkeypatch.setenv("CLEVERCLAUDE_API_PORT", "9000")
# Test that environment variables are respected
with patch("cleverclaude.core.app.CleverClaudeApp"):
runner.invoke(app, ["start"])
# Should use environment variables for config
# This would be verified in the actual app initialization
def test_cli_config_file_precedence(self, temp_dir):
"""Test configuration file precedence over defaults."""
runner = CliRunner()
# Create config file
config_dir = temp_dir / ".cleverclaude"
config_dir.mkdir()
config_file = config_dir / "config.yaml"
config_file.write_text("""
app:
name: "Custom CleverClaude"
environment: "testing"
api:
host: "custom.host.com"
port: 7777
""")
with patch("cleverclaude.core.app.CleverClaudeApp"):
runner.invoke(app, ["start", "--config-dir", str(config_dir)])
# Should use config file values
# This would be verified in the settings loading
+398
View File
@@ -0,0 +1,398 @@
"""Unit tests for CleverClaude MCP (Model Context Protocol) client."""
from unittest.mock import patch
import pytest
from cleverclaude.config.settings import Settings
from cleverclaude.mcp.client import MCPClient
from cleverclaude.mcp.protocol import MCPError, MCPMessage, MCPProtocol, MCPToolInfo
from cleverclaude.mcp.types import MCPToolExecutionResult
@pytest.mark.unit
@pytest.mark.async_test
class TestMCPClient:
"""Test suite for MCPClient class."""
async def test_initialization(self, test_settings: Settings):
"""Test MCPClient initialization."""
client = MCPClient(test_settings)
assert client.config == test_settings
assert client.protocol is not None
assert client.available_tools == {}
assert client.connected_servers == []
assert client._initialized is False
async def test_initialize_client(self, test_settings: Settings):
"""Test client initialization process."""
client = MCPClient(test_settings)
with patch.object(client, "_connect_to_servers") as mock_connect:
mock_connect.return_value = None
await client.initialize()
assert client._initialized is True
mock_connect.assert_called_once()
async def test_connect_to_servers(self, test_settings: Settings):
"""Test connection to MCP servers."""
client = MCPClient(test_settings)
mock_server_configs = [
{"name": "claude-flow-server", "url": "http://localhost:8001/mcp", "enabled": True},
{"name": "neural-server", "url": "http://localhost:8002/mcp", "enabled": True},
]
with patch.object(client, "_connect_server") as mock_connect_server:
mock_connect_server.return_value = {
"swarm_init": MCPToolInfo(name="swarm_init", description="Initialize swarm"),
"agent_spawn": MCPToolInfo(name="agent_spawn", description="Spawn agent"),
}
with patch.object(client.config, "mcp_servers", mock_server_configs):
await client._connect_to_servers()
assert len(client.connected_servers) == 2
assert "swarm_init" in client.available_tools
assert "agent_spawn" in client.available_tools
async def test_execute_tool_success(self, test_settings: Settings):
"""Test successful tool execution."""
client = MCPClient(test_settings)
await client.initialize()
# Mock tool availability
client.available_tools["swarm_init"] = MCPToolInfo(
name="swarm_init",
description="Initialize swarm",
parameters={"topology": {"type": "string"}, "maxAgents": {"type": "integer"}},
)
expected_result = {"swarm_id": "swarm_123", "topology": "mesh", "status": "created"}
with patch.object(client.protocol, "execute_tool") as mock_execute:
mock_execute.return_value = MCPToolExecutionResult(success=True, result=expected_result, error=None)
result = await client.execute_tool("swarm_init", {"topology": "mesh", "maxAgents": 5})
assert result.success is True
assert result.result == expected_result
mock_execute.assert_called_once_with("swarm_init", {"topology": "mesh", "maxAgents": 5})
async def test_execute_tool_not_found(self, test_settings: Settings):
"""Test executing non-existent tool."""
client = MCPClient(test_settings)
await client.initialize()
with pytest.raises(ValueError, match="Tool 'nonexistent_tool' not found"):
await client.execute_tool("nonexistent_tool", {})
async def test_execute_tool_with_error(self, test_settings: Settings):
"""Test tool execution with error."""
client = MCPClient(test_settings)
await client.initialize()
# Mock tool availability
client.available_tools["failing_tool"] = MCPToolInfo(name="failing_tool", description="Tool that fails")
with patch.object(client.protocol, "execute_tool") as mock_execute:
mock_execute.return_value = MCPToolExecutionResult(
success=False, result=None, error="Tool execution failed"
)
result = await client.execute_tool("failing_tool", {})
assert result.success is False
assert result.error == "Tool execution failed"
async def test_get_available_tools(self, test_settings: Settings):
"""Test getting list of available tools."""
client = MCPClient(test_settings)
await client.initialize()
# Add mock tools
client.available_tools = {
"swarm_init": MCPToolInfo(name="swarm_init", description="Initialize swarm"),
"agent_spawn": MCPToolInfo(name="agent_spawn", description="Spawn agent"),
"task_orchestrate": MCPToolInfo(name="task_orchestrate", description="Orchestrate task"),
}
tools = await client.get_available_tools()
assert len(tools) == 3
assert "swarm_init" in tools
assert "agent_spawn" in tools
assert "task_orchestrate" in tools
async def test_get_tool_info(self, test_settings: Settings):
"""Test getting tool information."""
client = MCPClient(test_settings)
await client.initialize()
tool_info = MCPToolInfo(
name="swarm_init",
description="Initialize swarm topology",
parameters={
"topology": {"type": "string", "enum": ["mesh", "hierarchical", "star", "ring"]},
"maxAgents": {"type": "integer", "minimum": 1, "maximum": 100},
},
)
client.available_tools["swarm_init"] = tool_info
retrieved_info = await client.get_tool_info("swarm_init")
assert retrieved_info == tool_info
assert retrieved_info.name == "swarm_init"
assert retrieved_info.description == "Initialize swarm topology"
assert "topology" in retrieved_info.parameters
assert "maxAgents" in retrieved_info.parameters
async def test_execute_multiple_tools(self, test_settings: Settings):
"""Test executing multiple tools in sequence."""
client = MCPClient(test_settings)
await client.initialize()
# Mock multiple tools
tools = ["swarm_init", "agent_spawn", "task_orchestrate"]
for tool in tools:
client.available_tools[tool] = MCPToolInfo(name=tool, description=f"Execute {tool}")
execution_sequence = [
("swarm_init", {"topology": "mesh"}),
("agent_spawn", {"type": "researcher"}),
("task_orchestrate", {"task": "test task"}),
]
results = []
with patch.object(client.protocol, "execute_tool") as mock_execute:
mock_execute.side_effect = [
MCPToolExecutionResult(success=True, result={"swarm_id": "swarm_1"}),
MCPToolExecutionResult(success=True, result={"agent_id": "agent_1"}),
MCPToolExecutionResult(success=True, result={"task_id": "task_1"}),
]
for tool_name, params in execution_sequence:
result = await 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["swarm_id"] == "swarm_1"
assert results[1].result["agent_id"] == "agent_1"
assert results[2].result["task_id"] == "task_1"
async def test_batch_execute_tools(self, test_settings: Settings):
"""Test batch execution of tools."""
client = MCPClient(test_settings)
await client.initialize()
# Mock tools
for tool in ["tool_1", "tool_2", "tool_3"]:
client.available_tools[tool] = MCPToolInfo(name=tool, description=f"Tool {tool}")
batch_requests = [
{"tool": "tool_1", "params": {"param": "value1"}},
{"tool": "tool_2", "params": {"param": "value2"}},
{"tool": "tool_3", "params": {"param": "value3"}},
]
with patch.object(client.protocol, "execute_tool") as mock_execute:
mock_execute.side_effect = [
MCPToolExecutionResult(success=True, result={"result": f"result_{i}"}) for i in range(3)
]
results = await client.batch_execute(batch_requests)
assert len(results) == 3
assert all(r.success for r in results)
assert mock_execute.call_count == 3
async def test_health_check(self, test_settings: Settings):
"""Test MCP client health check."""
client = MCPClient(test_settings)
await client.initialize()
client.connected_servers = ["server_1", "server_2"]
client.available_tools = {
"tool_1": MCPToolInfo(name="tool_1", description="Tool 1"),
"tool_2": MCPToolInfo(name="tool_2", description="Tool 2"),
}
with patch.object(client.protocol, "ping_server") as mock_ping:
mock_ping.return_value = True
health = await client.health_check()
assert health["status"] == "healthy"
assert health["connected_servers"] == 2
assert health["available_tools"] == 2
assert health["server_connectivity"]["server_1"] is True
assert health["server_connectivity"]["server_2"] is True
async def test_disconnect(self, test_settings: Settings):
"""Test client disconnection and cleanup."""
client = MCPClient(test_settings)
await client.initialize()
client.connected_servers = ["server_1", "server_2"]
with patch.object(client.protocol, "disconnect") as mock_disconnect:
await client.disconnect()
assert len(client.connected_servers) == 0
assert len(client.available_tools) == 0
assert client._initialized is False
mock_disconnect.assert_called_once()
@pytest.mark.unit
@pytest.mark.async_test
class TestMCPProtocol:
"""Test suite for MCPProtocol class."""
def test_create_message(self):
"""Test MCP message creation."""
protocol = MCPProtocol()
message = protocol.create_message(
method="tools/execute", params={"tool": "swarm_init", "arguments": {"topology": "mesh"}}
)
assert isinstance(message, MCPMessage)
assert message.method == "tools/execute"
assert message.params["tool"] == "swarm_init"
assert "id" in message.dict() # Should have generated ID
def test_parse_response_success(self):
"""Test parsing successful MCP response."""
protocol = MCPProtocol()
response_data = {
"id": "request_123",
"result": {"success": True, "data": {"swarm_id": "swarm_456", "status": "created"}},
}
result = protocol.parse_response(response_data)
assert result["success"] is True
assert result["data"]["swarm_id"] == "swarm_456"
def test_parse_response_error(self):
"""Test parsing MCP error response."""
protocol = MCPProtocol()
response_data = {
"id": "request_123",
"error": {"code": -32601, "message": "Method not found", "data": {"method": "unknown_method"}},
}
with pytest.raises(MCPError, match="Method not found"):
protocol.parse_response(response_data)
def test_validate_tool_parameters(self):
"""Test tool parameter validation."""
protocol = MCPProtocol()
tool_info = MCPToolInfo(
name="swarm_init",
description="Initialize swarm",
parameters={
"topology": {"type": "string", "enum": ["mesh", "hierarchical"]},
"maxAgents": {"type": "integer", "minimum": 1, "maximum": 100},
},
)
# Valid parameters
valid_params = {"topology": "mesh", "maxAgents": 10}
assert protocol.validate_parameters(tool_info, valid_params) is True
# Invalid topology
invalid_params = {"topology": "invalid", "maxAgents": 10}
assert protocol.validate_parameters(tool_info, invalid_params) is False
# Out of range maxAgents
invalid_params = {"topology": "mesh", "maxAgents": 200}
assert protocol.validate_parameters(tool_info, invalid_params) is False
async def test_execute_tool_with_retry(self):
"""Test tool execution with retry logic."""
protocol = MCPProtocol()
# Mock connection that fails first time, succeeds second time
call_count = 0
async def mock_send_request(message):
nonlocal call_count
call_count += 1
if call_count == 1:
raise ConnectionError("Network error")
return {"id": message.id, "result": {"success": True, "data": "success"}}
with patch.object(protocol, "_send_request", side_effect=mock_send_request):
result = await protocol.execute_tool("test_tool", {}, max_retries=2)
assert result.success is True
assert call_count == 2 # Should have retried once
@pytest.mark.unit
class TestMCPTypes:
"""Test suite for MCP type definitions."""
def test_mcp_message_creation(self):
"""Test MCPMessage creation."""
message = MCPMessage(method="tools/list", params={"category": "swarm"}, id="msg_123")
assert message.method == "tools/list"
assert message.params == {"category": "swarm"}
assert message.id == "msg_123"
def test_mcp_tool_info_creation(self):
"""Test MCPToolInfo creation."""
tool_info = MCPToolInfo(
name="agent_spawn",
description="Spawn a new agent",
parameters={
"type": {"type": "string", "enum": ["researcher", "coder", "analyst"]},
"name": {"type": "string"},
"capabilities": {"type": "array", "items": {"type": "string"}},
},
)
assert tool_info.name == "agent_spawn"
assert tool_info.description == "Spawn a new agent"
assert "type" in tool_info.parameters
assert "capabilities" in tool_info.parameters
def test_mcp_tool_execution_result(self):
"""Test MCPToolExecutionResult creation."""
# Successful result
success_result = MCPToolExecutionResult(
success=True, result={"agent_id": "agent_123", "status": "active"}, error=None, execution_time=0.5
)
assert success_result.success is True
assert success_result.result["agent_id"] == "agent_123"
assert success_result.error is None
assert success_result.execution_time == 0.5
# Error result
error_result = MCPToolExecutionResult(
success=False, result=None, error="Agent creation failed", execution_time=0.1
)
assert error_result.success is False
assert error_result.result is None
assert error_result.error == "Agent creation failed"
def test_mcp_error_creation(self):
"""Test MCPError exception creation."""
error = MCPError(code=-32601, message="Method not found", data={"method": "unknown_method"})
assert error.code == -32601
assert error.message == "Method not found"
assert error.data["method"] == "unknown_method"
assert "Method not found" in str(error)
+414
View File
@@ -0,0 +1,414 @@
"""Unit tests for CleverClaude swarm coordination."""
from datetime import datetime, timedelta
from unittest.mock import AsyncMock, patch
import pytest
from cleverclaude.agents.types import AgentType
from cleverclaude.config.settings import Settings
from cleverclaude.coordination.swarm import SwarmCoordinator
from cleverclaude.coordination.types import SwarmConfig, SwarmState, SwarmTask, SwarmTopology, TaskPriority
@pytest.mark.unit
@pytest.mark.async_test
class TestSwarmCoordinator:
"""Test suite for SwarmCoordinator class."""
async def test_initialization(self, test_settings: Settings, async_session, mock_redis):
"""Test SwarmCoordinator initialization."""
mock_agent_manager = AsyncMock()
coordinator = SwarmCoordinator(test_settings.swarm, async_session, mock_agent_manager, mock_redis)
assert coordinator.config == test_settings.swarm
assert coordinator.session == async_session
assert coordinator.agent_manager == mock_agent_manager
assert coordinator.redis == mock_redis
assert coordinator.swarms == {}
assert coordinator._initialized is False
async def test_initialize_coordinator(self, test_settings: Settings, async_session, mock_redis):
"""Test coordinator initialization process."""
mock_agent_manager = AsyncMock()
coordinator = SwarmCoordinator(test_settings.swarm, async_session, mock_agent_manager, mock_redis)
with patch("cleverclaude.coordination.swarm.structlog.get_logger") as mock_logger:
await coordinator.initialize()
assert coordinator._initialized is True
mock_logger.assert_called_once()
async def test_create_swarm_success(self, test_settings: Settings, async_session, mock_redis):
"""Test successful swarm creation."""
mock_agent_manager = AsyncMock()
coordinator = SwarmCoordinator(test_settings.swarm, async_session, mock_agent_manager, mock_redis)
await coordinator.initialize()
swarm_id = await coordinator.create_swarm(name="Test Swarm", topology=SwarmTopology.MESH, max_agents=10)
assert swarm_id in coordinator.swarms
swarm = coordinator.swarms[swarm_id]
assert swarm["name"] == "Test Swarm"
assert swarm["topology"] == SwarmTopology.MESH
assert swarm["state"] == SwarmState.ACTIVE
assert swarm["max_agents"] == 10
async def test_create_swarm_with_agents(self, test_settings: Settings, async_session, mock_redis):
"""Test swarm creation with initial agents."""
mock_agent_manager = AsyncMock()
coordinator = SwarmCoordinator(test_settings.swarm, async_session, mock_agent_manager, mock_redis)
await coordinator.initialize()
# Mock agent creation
mock_agent_ids = ["agent_1", "agent_2", "agent_3"]
mock_agent_manager.create_agent.side_effect = mock_agent_ids
swarm_id = await coordinator.create_swarm(
name="Test Swarm",
topology=SwarmTopology.HIERARCHICAL,
initial_agents=[
{"type": AgentType.COORDINATOR, "name": "coordinator"},
{"type": AgentType.RESEARCHER, "name": "researcher_1"},
{"type": AgentType.ANALYST, "name": "analyst_1"},
],
)
swarm = coordinator.swarms[swarm_id]
assert len(swarm["agents"]) == 3
assert mock_agent_manager.create_agent.call_count == 3
async def test_add_agent_to_swarm(self, test_settings: Settings, async_session, mock_redis):
"""Test adding an agent to an existing swarm."""
mock_agent_manager = AsyncMock()
coordinator = SwarmCoordinator(test_settings.swarm, async_session, mock_agent_manager, mock_redis)
await coordinator.initialize()
# Create swarm
swarm_id = await coordinator.create_swarm("Test Swarm", SwarmTopology.MESH)
# Add agent
agent_id = "test_agent_123"
await coordinator.add_agent(swarm_id, agent_id, role="worker")
swarm = coordinator.swarms[swarm_id]
assert len(swarm["agents"]) == 1
assert swarm["agents"][0]["agent_id"] == agent_id
assert swarm["agents"][0]["role"] == "worker"
async def test_add_agent_max_limit(self, test_settings: Settings, async_session, mock_redis):
"""Test adding agent when max limit is reached."""
mock_agent_manager = AsyncMock()
coordinator = SwarmCoordinator(test_settings.swarm, async_session, mock_agent_manager, mock_redis)
await coordinator.initialize()
# Create swarm with low max limit
swarm_id = await coordinator.create_swarm("Test Swarm", SwarmTopology.MESH, max_agents=1)
# Add first agent (should succeed)
await coordinator.add_agent(swarm_id, "agent_1", role="worker")
# Try to add second agent (should fail)
with pytest.raises(ValueError, match="Maximum number of agents reached"):
await coordinator.add_agent(swarm_id, "agent_2", role="worker")
async def test_remove_agent_from_swarm(self, test_settings: Settings, async_session, mock_redis):
"""Test removing an agent from swarm."""
mock_agent_manager = AsyncMock()
coordinator = SwarmCoordinator(test_settings.swarm, async_session, mock_agent_manager, mock_redis)
await coordinator.initialize()
# Create swarm and add agent
swarm_id = await coordinator.create_swarm("Test Swarm", SwarmTopology.MESH)
agent_id = "test_agent_123"
await coordinator.add_agent(swarm_id, agent_id, role="worker")
# Remove agent
await coordinator.remove_agent(swarm_id, agent_id)
swarm = coordinator.swarms[swarm_id]
assert len(swarm["agents"]) == 0
async def test_submit_task_to_swarm(self, test_settings: Settings, async_session, mock_redis):
"""Test submitting a task to swarm."""
mock_agent_manager = AsyncMock()
mock_agent_manager.execute_task.return_value = {"status": "completed", "result": "task done"}
coordinator = SwarmCoordinator(test_settings.swarm, async_session, mock_agent_manager, mock_redis)
await coordinator.initialize()
# Create swarm with agents
swarm_id = await coordinator.create_swarm("Test Swarm", SwarmTopology.MESH)
await coordinator.add_agent(swarm_id, "agent_1", role="worker")
# Submit task
task = SwarmTask(
task_type="analysis",
priority=TaskPriority.NORMAL,
data={"analysis_type": "data_analysis", "dataset": {"records": ["data_1", "data_2"]}},
)
task_id = await coordinator.submit_task(swarm_id, task)
assert task_id in coordinator.swarms[swarm_id]["tasks"]
task_info = coordinator.swarms[swarm_id]["tasks"][task_id]
assert task_info["task_type"] == "analysis"
assert task_info["status"] == "submitted"
async def test_task_distribution_mesh(self, test_settings: Settings, async_session, mock_redis):
"""Test task distribution in mesh topology."""
mock_agent_manager = AsyncMock()
mock_agent_manager.execute_task.return_value = {"status": "completed"}
coordinator = SwarmCoordinator(test_settings.swarm, async_session, mock_agent_manager, mock_redis)
await coordinator.initialize()
# Create mesh swarm with multiple agents
swarm_id = await coordinator.create_swarm("Mesh Swarm", SwarmTopology.MESH)
for i in range(3):
await coordinator.add_agent(swarm_id, f"agent_{i}", role="worker")
# Submit multiple tasks
tasks = []
for i in range(5):
task = SwarmTask(task_type="processing", priority=TaskPriority.NORMAL, data={"task_id": i})
task_id = await coordinator.submit_task(swarm_id, task)
tasks.append(task_id)
# Process tasks
await coordinator._process_pending_tasks(swarm_id)
# Verify tasks were distributed
swarm = coordinator.swarms[swarm_id]
completed_tasks = [t for t in swarm["tasks"].values() if t["status"] == "completed"]
assert len(completed_tasks) == 5
async def test_task_distribution_hierarchical(self, test_settings: Settings, async_session, mock_redis):
"""Test task distribution in hierarchical topology."""
mock_agent_manager = AsyncMock()
mock_agent_manager.execute_task.return_value = {"status": "completed"}
coordinator = SwarmCoordinator(test_settings.swarm, async_session, mock_agent_manager, mock_redis)
await coordinator.initialize()
# Create hierarchical swarm
swarm_id = await coordinator.create_swarm("Hierarchical Swarm", SwarmTopology.HIERARCHICAL)
# Add agents with hierarchy
await coordinator.add_agent(swarm_id, "coordinator", role="coordinator", level=0)
await coordinator.add_agent(swarm_id, "team_lead_1", role="team_lead", level=1)
await coordinator.add_agent(swarm_id, "worker_1", role="worker", level=2)
await coordinator.add_agent(swarm_id, "worker_2", role="worker", level=2)
# Submit complex task
task = SwarmTask(
task_type="complex_analysis",
priority=TaskPriority.HIGH,
data={"requires_coordination": True, "subtasks": 3},
)
task_id = await coordinator.submit_task(swarm_id, task)
# Process task
await coordinator._process_pending_tasks(swarm_id)
# Verify hierarchical processing
swarm = coordinator.swarms[swarm_id]
task_info = swarm["tasks"][task_id]
assert task_info["assigned_coordinator"] == "coordinator"
async def test_get_swarm_metrics(self, test_settings: Settings, async_session, mock_redis):
"""Test swarm metrics collection."""
mock_agent_manager = AsyncMock()
coordinator = SwarmCoordinator(test_settings.swarm, async_session, mock_agent_manager, mock_redis)
await coordinator.initialize()
# Create swarm with agents and tasks
swarm_id = await coordinator.create_swarm("Metrics Swarm", SwarmTopology.MESH)
await coordinator.add_agent(swarm_id, "agent_1", role="worker")
await coordinator.add_agent(swarm_id, "agent_2", role="worker")
# Simulate completed tasks
swarm = coordinator.swarms[swarm_id]
swarm["tasks"]["task_1"] = {"status": "completed", "completed_at": datetime.utcnow() - timedelta(minutes=5)}
swarm["tasks"]["task_2"] = {"status": "completed", "completed_at": datetime.utcnow() - timedelta(minutes=3)}
swarm["tasks"]["task_3"] = {"status": "running", "started_at": datetime.utcnow() - timedelta(minutes=1)}
metrics = await coordinator.get_swarm_metrics(swarm_id)
assert metrics.total_agents == 2
assert metrics.active_agents == 2
assert metrics.completed_tasks == 2
assert metrics.running_tasks == 1
assert metrics.efficiency_score > 0
async def test_scale_swarm_up(self, test_settings: Settings, async_session, mock_redis):
"""Test scaling swarm up (adding agents)."""
mock_agent_manager = AsyncMock()
mock_agent_manager.create_agent.return_value = "new_agent_123"
coordinator = SwarmCoordinator(test_settings.swarm, async_session, mock_agent_manager, mock_redis)
await coordinator.initialize()
# Create swarm with initial agents
swarm_id = await coordinator.create_swarm("Scalable Swarm", SwarmTopology.MESH, max_agents=10)
await coordinator.add_agent(swarm_id, "agent_1", role="worker")
# Scale up
await coordinator.scale_swarm(swarm_id, target_size=3)
swarm = coordinator.swarms[swarm_id]
assert len(swarm["agents"]) == 3
assert mock_agent_manager.create_agent.call_count == 2 # 2 new agents created
async def test_scale_swarm_down(self, test_settings: Settings, async_session, mock_redis):
"""Test scaling swarm down (removing agents)."""
mock_agent_manager = AsyncMock()
coordinator = SwarmCoordinator(test_settings.swarm, async_session, mock_agent_manager, mock_redis)
await coordinator.initialize()
# Create swarm with multiple agents
swarm_id = await coordinator.create_swarm("Scalable Swarm", SwarmTopology.MESH)
for i in range(5):
await coordinator.add_agent(swarm_id, f"agent_{i}", role="worker")
# Scale down
await coordinator.scale_swarm(swarm_id, target_size=2)
swarm = coordinator.swarms[swarm_id]
assert len(swarm["agents"]) == 2
async def test_swarm_health_monitoring(self, test_settings: Settings, async_session, mock_redis):
"""Test swarm health monitoring."""
mock_agent_manager = AsyncMock()
mock_agent_manager.get_agent_status.return_value = {
"status": "active",
"health": {"cpu_usage": 50, "memory_usage": 200, "task_count": 2},
}
coordinator = SwarmCoordinator(test_settings.swarm, async_session, mock_agent_manager, mock_redis)
await coordinator.initialize()
# Create swarm with agents
swarm_id = await coordinator.create_swarm("Health Swarm", SwarmTopology.MESH)
await coordinator.add_agent(swarm_id, "agent_1", role="worker")
await coordinator.add_agent(swarm_id, "agent_2", role="worker")
# Check health
health_report = await coordinator.check_swarm_health(swarm_id)
assert health_report["swarm_id"] == swarm_id
assert health_report["total_agents"] == 2
assert len(health_report["agent_health"]) == 2
assert health_report["overall_health"] in ["healthy", "degraded", "critical"]
async def test_handle_agent_failure(self, test_settings: Settings, async_session, mock_redis):
"""Test handling agent failure in swarm."""
mock_agent_manager = AsyncMock()
coordinator = SwarmCoordinator(test_settings.swarm, async_session, mock_agent_manager, mock_redis)
await coordinator.initialize()
# Create swarm with agents
swarm_id = await coordinator.create_swarm("Resilient Swarm", SwarmTopology.MESH)
await coordinator.add_agent(swarm_id, "agent_1", role="worker")
await coordinator.add_agent(swarm_id, "agent_2", role="worker")
# Simulate agent failure
await coordinator.handle_agent_failure(swarm_id, "agent_1")
swarm = coordinator.swarms[swarm_id]
failed_agent = next((a for a in swarm["agents"] if a["agent_id"] == "agent_1"), None)
assert failed_agent["status"] == "failed"
# Verify tasks are redistributed
# This would check that tasks assigned to failed agent are reassigned
async def test_destroy_swarm(self, test_settings: Settings, async_session, mock_redis):
"""Test swarm destruction and cleanup."""
mock_agent_manager = AsyncMock()
coordinator = SwarmCoordinator(test_settings.swarm, async_session, mock_agent_manager, mock_redis)
await coordinator.initialize()
# Create swarm with agents
swarm_id = await coordinator.create_swarm("Doomed Swarm", SwarmTopology.MESH)
await coordinator.add_agent(swarm_id, "agent_1", role="worker")
await coordinator.add_agent(swarm_id, "agent_2", role="worker")
# Destroy swarm
await coordinator.destroy_swarm(swarm_id)
# Verify cleanup
assert swarm_id not in coordinator.swarms
# Verify agents were destroyed/removed
assert mock_agent_manager.destroy_agent.call_count == 2
@pytest.mark.unit
class TestSwarmTypes:
"""Test suite for swarm type definitions."""
def test_swarm_topology_enum(self):
"""Test SwarmTopology enumeration."""
assert SwarmTopology.MESH == "mesh"
assert SwarmTopology.HIERARCHICAL == "hierarchical"
assert SwarmTopology.STAR == "star"
assert SwarmTopology.RING == "ring"
def test_swarm_state_enum(self):
"""Test SwarmState enumeration."""
assert SwarmState.INITIALIZING == "initializing"
assert SwarmState.ACTIVE == "active"
assert SwarmState.IDLE == "idle"
assert SwarmState.PAUSED == "paused"
assert SwarmState.SCALING == "scaling"
assert SwarmState.TERMINATED == "terminated"
def test_task_priority_enum(self):
"""Test TaskPriority enumeration."""
assert TaskPriority.LOW == "low"
assert TaskPriority.NORMAL == "normal"
assert TaskPriority.HIGH == "high"
assert TaskPriority.CRITICAL == "critical"
def test_swarm_task_creation(self):
"""Test SwarmTask creation and validation."""
task = SwarmTask(
task_type="analysis",
priority=TaskPriority.HIGH,
data={"analysis_type": "sentiment", "dataset": "customer_reviews"},
timeout=600,
dependencies=["task_1", "task_2"],
)
assert task.task_type == "analysis"
assert task.priority == TaskPriority.HIGH
assert task.data["analysis_type"] == "sentiment"
assert task.timeout == 600
assert task.dependencies == ["task_1", "task_2"]
def test_swarm_config_creation(self):
"""Test SwarmConfig creation and validation."""
config = SwarmConfig(
name="Test Swarm",
topology=SwarmTopology.HIERARCHICAL,
max_agents=20,
coordination_timeout=120,
load_balancing=True,
)
assert config.name == "Test Swarm"
assert config.topology == SwarmTopology.HIERARCHICAL
assert config.max_agents == 20
assert config.coordination_timeout == 120
assert config.load_balancing is True
def test_swarm_config_defaults(self):
"""Test SwarmConfig default values."""
config = SwarmConfig(name="Default Swarm", topology=SwarmTopology.MESH)
assert config.max_agents == 50 # Default
assert config.coordination_timeout == 60 # Default
assert config.load_balancing is True # Default