"""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