| from unittest.mock import AsyncMock, MagicMock, patch |
|
|
| import pytest |
|
|
| from src.server.services.agent_service import AgentService |
|
|
|
|
| @pytest.mark.asyncio |
| |
| @pytest.mark.asyncio |
| |
| @patch("src.server.services.projects.task_service.task_service", new_callable=AsyncMock) |
| @patch("src.server.services.agent_service.get_logger") |
| async def test_run_agent_task_enqueues(mock_get_logger, mock_task_service): |
| """ |
| Unit tests the AgentService.run_agent_task method for default enqueuing. |
| Verifies that it sets status to 'dispatched'. |
| """ |
| |
| mock_logger = MagicMock() |
| mock_get_logger.return_value = mock_logger |
| mock_task_service.update_task.return_value = (True, {}) |
|
|
| agent_service = AgentService() |
| task_id = "task-123" |
| agent_id = "ai-test-agent" |
|
|
| |
| await agent_service.run_agent_task(task_id, agent_id, immediate=False) |
|
|
| |
| |
| mock_logger.info.assert_any_call(f"📥 Enqueuing task '{task_id}' for AI agent '{agent_id}'.") |
|
|
| |
| mock_task_service.update_task.assert_awaited_once_with(task_id, {"status": "dispatched", "assignee": agent_id}) |
|
|
|
|
| @pytest.mark.asyncio |
| @patch("src.server.services.projects.task_service.task_service", new_callable=AsyncMock) |
| @patch("src.server.services.agent_service.get_logger") |
| async def test_run_agent_task_immediate(mock_get_logger, mock_task_service): |
| """ |
| Tests immediate execution (bypassing queue). |
| """ |
| mock_logger = MagicMock() |
| mock_get_logger.return_value = mock_logger |
| mock_task_service.update_task.return_value = (True, {}) |
|
|
| agent_service = AgentService() |
| task_id = "task-123" |
| agent_id = "ai-test-agent" |
|
|
| with patch.object(AgentService, "_run_general_agent_task", new_callable=AsyncMock) as mock_general_loop: |
| await agent_service.run_agent_task(task_id, agent_id, immediate=True) |
| mock_general_loop.assert_awaited_once_with(task_id, agent_id) |
|
|
| |
| mock_logger.info.assert_any_call(f"🚀 AI agent '{agent_id}' starting physical work on task '{task_id}'.") |
| mock_task_service.update_task.assert_awaited_once_with(task_id, {"status": "doing", "assignee": agent_id}) |
|
|
|
|
| @pytest.mark.asyncio |
| @patch("src.server.services.projects.task_service.task_service", new_callable=AsyncMock) |
| @patch("src.server.services.agent_service.get_logger") |
| async def test_run_agent_task_fails_to_enqueue(mock_get_logger, mock_task_service): |
| """ |
| Tests failure handling when enqueuing. |
| """ |
| |
| mock_logger = MagicMock() |
| mock_get_logger.return_value = mock_logger |
| mock_task_service.update_task.return_value = (False, {"error": "DB down"}) |
|
|
| agent_service = AgentService() |
| task_id = "task-456" |
| agent_id = "ai-failing-agent" |
|
|
| |
| await agent_service.run_agent_task(task_id, agent_id) |
|
|
| |
| mock_logger.info.assert_called_once_with(f"📥 Enqueuing task '{task_id}' for AI agent '{agent_id}'.") |
| mock_logger.error.assert_called_once_with("Failed to enqueue task: DB down") |
|
|
|
|
| @pytest.mark.asyncio |
| @patch("src.server.services.dev_ops_agent_service.asyncio.create_subprocess_shell") |
| @patch("src.server.services.dev_ops_agent_service.get_logger") |
| async def test_run_command_success(mock_get_logger, mock_subprocess): |
| """ |
| Test run_command_with_self_healing when command succeeds. |
| """ |
| mock_logger = MagicMock() |
| mock_get_logger.return_value = mock_logger |
|
|
| |
| mock_process = AsyncMock() |
| mock_process.communicate.return_value = (b"success output", b"") |
| mock_process.returncode = 0 |
| mock_subprocess.return_value = mock_process |
|
|
| agent_service = AgentService() |
| success, output = await agent_service.dev_ops.run_command_with_self_healing("echo test", agent_id="test-bot") |
|
|
| assert success is True |
| assert output == "success output" |
| mock_logger.info.assert_called_with("Command 'echo test' succeeded initially.") |
|
|
|
|
| @pytest.mark.asyncio |
| @patch("src.server.services.dev_ops_agent_service.credential_service") |
| @patch("src.server.services.dev_ops_agent_service.get_llm_client") |
| @patch("src.server.services.dev_ops_agent_service.asyncio.create_subprocess_shell") |
| @patch("src.server.services.dev_ops_agent_service.get_logger") |
| async def test_run_command_failure_triggers_healing( |
| mock_get_logger, mock_subprocess, mock_get_llm, mock_credential_service |
| ): |
| """ |
| Test run_command_with_self_healing when command fails. |
| Verifies that it calls the LLM for analysis. |
| """ |
| mock_logger = MagicMock() |
| mock_get_logger.return_value = mock_logger |
|
|
| |
| mock_process = AsyncMock() |
| mock_process.communicate.return_value = (b"", b"Error: command failed") |
| mock_process.returncode = 1 |
| mock_subprocess.return_value = mock_process |
|
|
| |
| mock_credential_service.get_active_provider = AsyncMock(return_value={"chat_model": "gpt-4-test"}) |
| |
| mock_credential_service.get_credential = AsyncMock(return_value="test-api-key") |
|
|
| |
| mock_client = AsyncMock() |
| mock_get_llm.return_value.__aenter__.return_value = mock_client |
|
|
| mock_response = MagicMock() |
| |
| mock_response.choices = [MagicMock(message=MagicMock(content='{"reasoning": "Check syntax"}'))] |
| mock_client.chat.completions.create.return_value = mock_response |
|
|
| agent_service = AgentService() |
| success, output = await agent_service.dev_ops.run_command_with_self_healing("failing_cmd", agent_id="test-bot") |
|
|
| assert success is False |
| |
| assert "Poisson Security Block" in output |
| assert "Proposal: Check syntax" in output |
|
|
| |
| mock_client.chat.completions.create.assert_awaited_once() |
| |
| mock_logger.warning.assert_any_call("Command 'failing_cmd' failed. Starting Active Repair Loop.") |
| mock_logger.warning.assert_any_call( |
| "Poisson Gate: Insufficient credibility for Level 2 auto-repair. (Unknown (XP 0))" |
| ) |
|
|