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