| """ |
| Agent Control Routes Test Suite |
| |
| Comprehensive TestClient-based coverage for agent_control_routes.py. |
| Tests all 5 endpoints: start, stop, restart, status, execute. |
| |
| Coverage Target: 75%+ line coverage |
| Test Count: 53 tests |
| """ |
|
|
| import pytest |
| from fastapi import FastAPI |
| from fastapi.testclient import TestClient |
| from unittest.mock import MagicMock, patch |
| from typing import Dict, Any |
|
|
| |
| import sys |
| from pathlib import Path |
| sys.path.insert(0, str(Path(__file__).parent.parent.parent)) |
| from api.agent_control_routes import router |
| from core.admin_endpoints import get_super_admin |
| from core.models import User |
|
|
|
|
| |
| |
| |
|
|
| @pytest.fixture(scope="function") |
| def agent_control_client() -> TestClient: |
| """ |
| Create TestClient with agent_control_routes router. |
| |
| Overrides the get_super_admin dependency to return our test super admin user. |
| Isolated app instance avoids SQLAlchemy metadata conflicts. |
| """ |
| |
| super_admin_user = User( |
| id="test-super-admin", |
| email="superadmin@test.com", |
| role="super_admin" |
| ) |
|
|
| def override_get_super_admin(): |
| return super_admin_user |
|
|
| app = FastAPI() |
| app.include_router(router) |
| app.dependency_overrides[get_super_admin] = override_get_super_admin |
|
|
| client = TestClient(app) |
| try: |
| yield client |
| finally: |
| app.dependency_overrides.clear() |
|
|
|
|
| |
| |
| |
|
|
| class TestAgentStart: |
| """Test suite for POST /api/agent/start endpoint.""" |
|
|
| def test_start_success(self, agent_control_client: TestClient): |
| """Test successful daemon start with default parameters.""" |
| with patch('api.agent_control_routes.DaemonManager') as mock_dm: |
| mock_dm.is_running.return_value = False |
| mock_dm.start_daemon.return_value = 12345 |
|
|
| response = agent_control_client.post("/api/agent/start", json={}) |
|
|
| assert response.status_code == 200 |
| data = response.json() |
| assert data["success"] is True |
| assert data["pid"] == 12345 |
| assert data["status"] == "started" |
| assert data["dashboard_url"] == "http://0.0.0.0:8000" |
| assert "message" in data |
|
|
| def test_start_with_custom_port(self, agent_control_client: TestClient): |
| """Test daemon start with custom port.""" |
| with patch('api.agent_control_routes.DaemonManager') as mock_dm: |
| mock_dm.is_running.return_value = False |
| mock_dm.start_daemon.return_value = 12345 |
|
|
| response = agent_control_client.post( |
| "/api/agent/start", |
| json={"port": 9000} |
| ) |
|
|
| assert response.status_code == 200 |
| data = response.json() |
| assert data["dashboard_url"] == "http://0.0.0.0:9000" |
| mock_dm.start_daemon.assert_called_once_with( |
| port=9000, |
| host="0.0.0.0", |
| workers=1, |
| host_mount=False, |
| dev=False |
| ) |
|
|
| def test_start_with_custom_host(self, agent_control_client: TestClient): |
| """Test daemon start with custom host.""" |
| with patch('api.agent_control_routes.DaemonManager') as mock_dm: |
| mock_dm.is_running.return_value = False |
| mock_dm.start_daemon.return_value = 12345 |
|
|
| response = agent_control_client.post( |
| "/api/agent/start", |
| json={"host": "127.0.0.1"} |
| ) |
|
|
| assert response.status_code == 200 |
| data = response.json() |
| assert data["dashboard_url"] == "http://127.0.0.1:8000" |
|
|
| def test_start_with_workers(self, agent_control_client: TestClient): |
| """Test daemon start with multiple workers.""" |
| with patch('api.agent_control_routes.DaemonManager') as mock_dm: |
| mock_dm.is_running.return_value = False |
| mock_dm.start_daemon.return_value = 12345 |
|
|
| response = agent_control_client.post( |
| "/api/agent/start", |
| json={"workers": 4} |
| ) |
|
|
| assert response.status_code == 200 |
| mock_dm.start_daemon.assert_called_once_with( |
| port=8000, |
| host="0.0.0.0", |
| workers=4, |
| host_mount=False, |
| dev=False |
| ) |
|
|
| def test_start_with_host_mount(self, agent_control_client: TestClient): |
| """Test daemon start with host filesystem mount enabled.""" |
| with patch('api.agent_control_routes.DaemonManager') as mock_dm: |
| mock_dm.is_running.return_value = False |
| mock_dm.start_daemon.return_value = 12345 |
|
|
| response = agent_control_client.post( |
| "/api/agent/start", |
| json={"host_mount": True} |
| ) |
|
|
| assert response.status_code == 200 |
| mock_dm.start_daemon.assert_called_once_with( |
| port=8000, |
| host="0.0.0.0", |
| workers=1, |
| host_mount=True, |
| dev=False |
| ) |
|
|
| def test_start_with_dev_mode(self, agent_control_client: TestClient): |
| """Test daemon start with development mode enabled.""" |
| with patch('api.agent_control_routes.DaemonManager') as mock_dm: |
| mock_dm.is_running.return_value = False |
| mock_dm.start_daemon.return_value = 12345 |
|
|
| response = agent_control_client.post( |
| "/api/agent/start", |
| json={"dev": True} |
| ) |
|
|
| assert response.status_code == 200 |
| mock_dm.start_daemon.assert_called_once_with( |
| port=8000, |
| host="0.0.0.0", |
| workers=1, |
| host_mount=False, |
| dev=True |
| ) |
|
|
| def test_start_already_running(self, agent_control_client: TestClient): |
| """Test start returns 400 when daemon already running.""" |
| with patch('api.agent_control_routes.DaemonManager') as mock_dm: |
| mock_dm.is_running.return_value = True |
| mock_dm.get_pid.return_value = 9999 |
|
|
| response = agent_control_client.post("/api/agent/start", json={}) |
|
|
| assert response.status_code == 400 |
| data = response.json() |
| assert "already running" in data["detail"].lower() |
| assert "9999" in data["detail"] |
|
|
| def test_start_returns_dashboard_url(self, agent_control_client: TestClient): |
| """Test start response includes dashboard URL.""" |
| with patch('api.agent_control_routes.DaemonManager') as mock_dm: |
| mock_dm.is_running.return_value = False |
| mock_dm.start_daemon.return_value = 12345 |
|
|
| response = agent_control_client.post( |
| "/api/agent/start", |
| json={"host": "localhost", "port": 8080} |
| ) |
|
|
| assert response.status_code == 200 |
| data = response.json() |
| assert data["dashboard_url"] == "http://localhost:8080" |
|
|
| def test_start_invalid_port_too_high(self, agent_control_client: TestClient): |
| """Test start returns 422 for port > 65535.""" |
| response = agent_control_client.post( |
| "/api/agent/start", |
| json={"port": 70000} |
| ) |
|
|
| assert response.status_code == 422 |
|
|
| def test_start_invalid_port_zero(self, agent_control_client: TestClient): |
| """Test start returns 422 for port 0.""" |
| response = agent_control_client.post( |
| "/api/agent/start", |
| json={"port": 0} |
| ) |
|
|
| assert response.status_code == 422 |
|
|
| def test_start_invalid_port_negative(self, agent_control_client: TestClient): |
| """Test start returns 422 for negative port.""" |
| response = agent_control_client.post( |
| "/api/agent/start", |
| json={"port": -1} |
| ) |
|
|
| assert response.status_code == 422 |
|
|
| def test_start_invalid_workers_too_many(self, agent_control_client: TestClient): |
| """Test start returns 422 for workers > 16.""" |
| response = agent_control_client.post( |
| "/api/agent/start", |
| json={"workers": 17} |
| ) |
|
|
| assert response.status_code == 422 |
|
|
| def test_start_invalid_workers_zero(self, agent_control_client: TestClient): |
| """Test start returns 422 for workers 0.""" |
| response = agent_control_client.post( |
| "/api/agent/start", |
| json={"workers": 0} |
| ) |
|
|
| assert response.status_code == 422 |
|
|
| def test_start_boundary_port_values(self, agent_control_client: TestClient): |
| """Test start with boundary port values (1, 65535).""" |
| with patch('api.agent_control_routes.DaemonManager') as mock_dm: |
| mock_dm.is_running.return_value = False |
| mock_dm.start_daemon.return_value = 12345 |
|
|
| |
| response1 = agent_control_client.post( |
| "/api/agent/start", |
| json={"port": 1} |
| ) |
| assert response1.status_code == 200 |
|
|
| |
| response2 = agent_control_client.post( |
| "/api/agent/start", |
| json={"port": 65535} |
| ) |
| assert response2.status_code == 200 |
|
|
| def test_start_runtime_error(self, agent_control_client: TestClient): |
| """Test start returns 500 on RuntimeError from start_daemon.""" |
| with patch('api.agent_control_routes.DaemonManager') as mock_dm: |
| mock_dm.is_running.return_value = False |
| mock_dm.start_daemon.side_effect = RuntimeError("Port already in use") |
|
|
| response = agent_control_client.post("/api/agent/start", json={}) |
|
|
| assert response.status_code == 500 |
| data = response.json() |
| assert "Port already in use" in data["detail"] |
|
|
| def test_start_io_error(self, agent_control_client: TestClient): |
| """Test start returns 500 on IOError from start_daemon.""" |
| with patch('api.agent_control_routes.DaemonManager') as mock_dm: |
| mock_dm.is_running.return_value = False |
| mock_dm.start_daemon.side_effect = IOError("Cannot write PID file") |
|
|
| response = agent_control_client.post("/api/agent/start", json={}) |
|
|
| assert response.status_code == 500 |
| data = response.json() |
| assert "Cannot write PID file" in data["detail"] |
|
|
|
|
| |
| |
| |
|
|
| class TestAgentStop: |
| """Test suite for POST /api/agent/stop endpoint.""" |
|
|
| def test_stop_success(self, agent_control_client: TestClient): |
| """Test successful daemon stop.""" |
| with patch('api.agent_control_routes.DaemonManager') as mock_dm: |
| mock_dm.is_running.return_value = True |
|
|
| response = agent_control_client.post("/api/agent/stop", json={}) |
|
|
| assert response.status_code == 200 |
| data = response.json() |
| assert data["success"] is True |
| assert data["status"] == "stopped" |
| assert "message" in data |
| mock_dm.stop_daemon.assert_called_once() |
|
|
| def test_stop_not_running(self, agent_control_client: TestClient): |
| """Test stop returns 400 when daemon not running.""" |
| with patch('api.agent_control_routes.DaemonManager') as mock_dm: |
| mock_dm.is_running.return_value = False |
|
|
| response = agent_control_client.post("/api/agent/stop", json={}) |
|
|
| assert response.status_code == 400 |
| data = response.json() |
| assert "not running" in data["detail"].lower() |
|
|
| def test_stop_calls_stop_daemon(self, agent_control_client: TestClient): |
| """Test stop endpoint calls DaemonManager.stop_daemon().""" |
| with patch('api.agent_control_routes.DaemonManager') as mock_dm: |
| mock_dm.is_running.return_value = True |
|
|
| agent_control_client.post("/api/agent/stop", json={}) |
|
|
| mock_dm.stop_daemon.assert_called_once() |
|
|
| def test_stop_response_format(self, agent_control_client: TestClient): |
| """Test stop response has correct format.""" |
| with patch('api.agent_control_routes.DaemonManager') as mock_dm: |
| mock_dm.is_running.return_value = True |
|
|
| response = agent_control_client.post("/api/agent/stop", json={}) |
|
|
| assert response.status_code == 200 |
| data = response.json() |
| assert data["success"] is True |
| assert data["status"] == "stopped" |
| assert "message" in data |
| assert data["message"] == "Atom OS stopped successfully" |
|
|
| def test_stop_exception_handling(self, agent_control_client: TestClient): |
| """Test stop returns 500 on unexpected exception.""" |
| with patch('api.agent_control_routes.DaemonManager') as mock_dm: |
| mock_dm.is_running.return_value = True |
| mock_dm.stop_daemon.side_effect = Exception("Unexpected error") |
|
|
| response = agent_control_client.post("/api/agent/stop", json={}) |
|
|
| assert response.status_code == 500 |
| data = response.json() |
| assert "Unexpected error" in data["detail"] |
|
|
| def test_stop_when_already_stopped(self, agent_control_client: TestClient): |
| """Test stop handles multiple stop calls gracefully (idempotency).""" |
| with patch('api.agent_control_routes.DaemonManager') as mock_dm: |
| mock_dm.is_running.return_value = False |
|
|
| |
| response1 = agent_control_client.post("/api/agent/stop", json={}) |
| assert response1.status_code == 400 |
|
|
| |
| response2 = agent_control_client.post("/api/agent/stop", json={}) |
| assert response2.status_code == 400 |
|
|
|
|
| |
| |
| |
|
|
| class TestAgentRestart: |
| """Test suite for POST /api/agent/restart endpoint.""" |
|
|
| def test_restart_when_running(self, agent_control_client: TestClient): |
| """Test restart stops then starts daemon when running.""" |
| with patch('api.agent_control_routes.DaemonManager') as mock_dm: |
| mock_dm.is_running.return_value = True |
| mock_dm.start_daemon.return_value = 54321 |
|
|
| response = agent_control_client.post("/api/agent/restart", json={}) |
|
|
| assert response.status_code == 200 |
| data = response.json() |
| assert data["success"] is True |
| assert data["pid"] == 54321 |
| assert data["status"] == "restarted" |
| assert data["was_running"] is True |
| mock_dm.stop_daemon.assert_called_once() |
| mock_dm.start_daemon.assert_called_once() |
|
|
| def test_restart_when_not_running(self, agent_control_client: TestClient): |
| """Test restart starts daemon without stopping when not running.""" |
| with patch('api.agent_control_routes.DaemonManager') as mock_dm: |
| mock_dm.is_running.return_value = False |
| mock_dm.start_daemon.return_value = 54321 |
|
|
| response = agent_control_client.post("/api/agent/restart", json={}) |
|
|
| assert response.status_code == 200 |
| data = response.json() |
| assert data["success"] is True |
| assert data["was_running"] is False |
| mock_dm.stop_daemon.assert_not_called() |
| mock_dm.start_daemon.assert_called_once() |
|
|
| def test_restart_with_new_config(self, agent_control_client: TestClient): |
| """Test restart applies new port/host configuration.""" |
| with patch('api.agent_control_routes.DaemonManager') as mock_dm: |
| mock_dm.is_running.return_value = False |
| mock_dm.start_daemon.return_value = 54321 |
|
|
| response = agent_control_client.post( |
| "/api/agent/restart", |
| json={"port": 9000, "host": "127.0.0.1"} |
| ) |
|
|
| assert response.status_code == 200 |
| data = response.json() |
| assert data["dashboard_url"] == "http://127.0.0.1:9000" |
|
|
| def test_restart_was_running_true(self, agent_control_client: TestClient): |
| """Test restart returns was_running=True when daemon was running.""" |
| with patch('api.agent_control_routes.DaemonManager') as mock_dm: |
| mock_dm.is_running.return_value = True |
| mock_dm.start_daemon.return_value = 54321 |
|
|
| response = agent_control_client.post("/api/agent/restart", json={}) |
|
|
| assert response.status_code == 200 |
| data = response.json() |
| assert data["was_running"] is True |
|
|
| def test_restart_was_running_false(self, agent_control_client: TestClient): |
| """Test restart returns was_running=False when daemon was not running.""" |
| with patch('api.agent_control_routes.DaemonManager') as mock_dm: |
| mock_dm.is_running.return_value = False |
| mock_dm.start_daemon.return_value = 54321 |
|
|
| response = agent_control_client.post("/api/agent/restart", json={}) |
|
|
| assert response.status_code == 200 |
| data = response.json() |
| assert data["was_running"] is False |
|
|
| def test_restart_returns_new_pid(self, agent_control_client: TestClient): |
| """Test restart returns new PID after restart.""" |
| with patch('api.agent_control_routes.DaemonManager') as mock_dm: |
| mock_dm.is_running.return_value = False |
| mock_dm.start_daemon.return_value = 54321 |
|
|
| response = agent_control_client.post("/api/agent/restart", json={}) |
|
|
| assert response.status_code == 200 |
| data = response.json() |
| assert data["pid"] == 54321 |
|
|
| def test_restart_exception_handling(self, agent_control_client: TestClient): |
| """Test restart returns 500 on unexpected exception.""" |
| with patch('api.agent_control_routes.DaemonManager') as mock_dm: |
| mock_dm.is_running.side_effect = Exception("Daemon error") |
|
|
| response = agent_control_client.post("/api/agent/restart", json={}) |
|
|
| assert response.status_code == 500 |
| data = response.json() |
| assert "Daemon error" in data["detail"] |
|
|
|
|
| class TestAgentStatus: |
| """Test suite for GET /api/agent/status endpoint.""" |
|
|
| def test_status_when_running(self, agent_control_client: TestClient): |
| """Test status returns running status when daemon is running.""" |
| test_status = { |
| "running": True, |
| "pid": 12345, |
| "uptime_seconds": 3600, |
| "memory_mb": 256.5, |
| "cpu_percent": 5.2, |
| "status": "running" |
| } |
|
|
| with patch('api.agent_control_routes.DaemonManager') as mock_dm: |
| mock_dm.get_status.return_value = test_status |
|
|
| response = agent_control_client.get("/api/agent/status") |
|
|
| assert response.status_code == 200 |
| data = response.json() |
| assert data["success"] is True |
| assert data["status"]["running"] is True |
|
|
| def test_status_when_not_running(self, agent_control_client: TestClient): |
| """Test status returns non-running status when daemon is not running.""" |
| test_status = { |
| "running": False, |
| "pid": None, |
| "uptime_seconds": None, |
| "memory_mb": None, |
| "cpu_percent": None, |
| "status": "not_running" |
| } |
|
|
| with patch('api.agent_control_routes.DaemonManager') as mock_dm: |
| mock_dm.get_status.return_value = test_status |
|
|
| response = agent_control_client.get("/api/agent/status") |
|
|
| assert response.status_code == 200 |
| data = response.json() |
| assert data["success"] is True |
| assert data["status"]["running"] is False |
|
|
| def test_status_includes_pid(self, agent_control_client: TestClient): |
| """Test status response includes PID.""" |
| test_status = { |
| "running": True, |
| "pid": 12345, |
| "uptime_seconds": 3600, |
| "memory_mb": 256.5, |
| "cpu_percent": 5.2, |
| "status": "running" |
| } |
|
|
| with patch('api.agent_control_routes.DaemonManager') as mock_dm: |
| mock_dm.get_status.return_value = test_status |
|
|
| response = agent_control_client.get("/api/agent/status") |
|
|
| assert response.status_code == 200 |
| data = response.json() |
| assert "pid" in data["status"] |
| assert data["status"]["pid"] == 12345 |
|
|
| def test_status_includes_uptime(self, agent_control_client: TestClient): |
| """Test status response includes uptime_seconds.""" |
| test_status = { |
| "running": True, |
| "pid": 12345, |
| "uptime_seconds": 3600, |
| "memory_mb": 256.5, |
| "cpu_percent": 5.2, |
| "status": "running" |
| } |
|
|
| with patch('api.agent_control_routes.DaemonManager') as mock_dm: |
| mock_dm.get_status.return_value = test_status |
|
|
| response = agent_control_client.get("/api/agent/status") |
|
|
| assert response.status_code == 200 |
| data = response.json() |
| assert "uptime_seconds" in data["status"] |
| assert data["status"]["uptime_seconds"] == 3600 |
|
|
| def test_status_includes_memory(self, agent_control_client: TestClient): |
| """Test status response includes memory_mb.""" |
| test_status = { |
| "running": True, |
| "pid": 12345, |
| "uptime_seconds": 3600, |
| "memory_mb": 256.5, |
| "cpu_percent": 5.2, |
| "status": "running" |
| } |
|
|
| with patch('api.agent_control_routes.DaemonManager') as mock_dm: |
| mock_dm.get_status.return_value = test_status |
|
|
| response = agent_control_client.get("/api/agent/status") |
|
|
| assert response.status_code == 200 |
| data = response.json() |
| assert "memory_mb" in data["status"] |
| assert data["status"]["memory_mb"] == 256.5 |
|
|
| def test_status_includes_cpu(self, agent_control_client: TestClient): |
| """Test status response includes cpu_percent.""" |
| test_status = { |
| "running": True, |
| "pid": 12345, |
| "uptime_seconds": 3600, |
| "memory_mb": 256.5, |
| "cpu_percent": 5.2, |
| "status": "running" |
| } |
|
|
| with patch('api.agent_control_routes.DaemonManager') as mock_dm: |
| mock_dm.get_status.return_value = test_status |
|
|
| response = agent_control_client.get("/api/agent/status") |
|
|
| assert response.status_code == 200 |
| data = response.json() |
| assert "cpu_percent" in data["status"] |
| assert data["status"]["cpu_percent"] == 5.2 |
|
|
| def test_status_exception_handling(self, agent_control_client: TestClient): |
| """Test status returns 500 on get_status() exception.""" |
| with patch('api.agent_control_routes.DaemonManager') as mock_dm: |
| mock_dm.get_status.side_effect = Exception("Status error") |
|
|
| response = agent_control_client.get("/api/agent/status") |
|
|
| assert response.status_code == 500 |
| data = response.json() |
| assert "Status error" in data["detail"] |
|
|
|
|
| |
| |
| |
|
|
| class TestAgentExecute: |
| """Test suite for POST /api/agent/execute endpoint.""" |
|
|
| def test_execute_placeholder(self, agent_control_client: TestClient): |
| """Test execute returns placeholder response (not yet implemented).""" |
| response = agent_control_client.post( |
| "/api/agent/execute", |
| json={"command": "agent.chat('Hello')"} |
| ) |
|
|
| assert response.status_code == 200 |
| data = response.json() |
| assert data["success"] is True |
| assert data["result"] == "Command execution not yet implemented" |
|
|
| def test_execute_response_format(self, agent_control_client: TestClient): |
| """Test execute response has correct format.""" |
| response = agent_control_client.post( |
| "/api/agent/execute", |
| json={"command": "agent.status()"} |
| ) |
|
|
| assert response.status_code == 200 |
| data = response.json() |
| assert "success" in data |
| assert "result" in data |
| assert "note" in data |
| assert "Use POST /api/agent/start" in data["note"] |
|
|
| def test_execute_command_validation(self, agent_control_client: TestClient): |
| """Test execute requires command field.""" |
| response = agent_control_client.post( |
| "/api/agent/execute", |
| json={} |
| ) |
|
|
| assert response.status_code == 422 |
|
|
| def test_execute_timeout_validation(self, agent_control_client: TestClient): |
| """Test execute validates timeout constraints (1-300 seconds).""" |
| response = agent_control_client.post( |
| "/api/agent/execute", |
| json={"command": "agent.chat('Hello')", "timeout": 30} |
| ) |
|
|
| assert response.status_code == 200 |
|
|
| def test_execute_invalid_timeout_low(self, agent_control_client: TestClient): |
| """Test execute returns 422 for timeout < 1.""" |
| response = agent_control_client.post( |
| "/api/agent/execute", |
| json={"command": "agent.chat('Hello')", "timeout": 0} |
| ) |
|
|
| assert response.status_code == 422 |
|
|
| def test_execute_invalid_timeout_high(self, agent_control_client: TestClient): |
| """Test execute returns 422 for timeout > 300.""" |
| response = agent_control_client.post( |
| "/api/agent/execute", |
| json={"command": "agent.chat('Hello')", "timeout": 301} |
| ) |
|
|
| assert response.status_code == 422 |
|
|
| def test_execute_boundary_timeout_values(self, agent_control_client: TestClient): |
| """Test execute with boundary timeout values (1, 300).""" |
| |
| response1 = agent_control_client.post( |
| "/api/agent/execute", |
| json={"command": "agent.chat('Hello')", "timeout": 1} |
| ) |
| assert response1.status_code == 200 |
|
|
| |
| response2 = agent_control_client.post( |
| "/api/agent/execute", |
| json={"command": "agent.chat('Hello')", "timeout": 300} |
| ) |
| assert response2.status_code == 200 |
|
|
|
|
| |
| |
| |
|
|
| class TestAgentControlErrorPaths: |
| """Test error paths and edge cases for agent control routes.""" |
|
|
| def test_start_permission_error(self, agent_control_client: TestClient): |
| """Test start handles permission errors gracefully.""" |
| with patch('api.agent_control_routes.DaemonManager') as mock_dm: |
| mock_dm.is_running.return_value = False |
| mock_dm.start_daemon.side_effect = PermissionError("Permission denied") |
|
|
| response = agent_control_client.post("/api/agent/start", json={}) |
|
|
| assert response.status_code == 500 |
|
|
| def test_concurrent_start_requests(self, agent_control_client: TestClient): |
| """Test multiple simultaneous start requests (first wins).""" |
| with patch('api.agent_control_routes.DaemonManager') as mock_dm: |
| mock_dm.is_running.return_value = False |
| mock_dm.start_daemon.return_value = 12345 |
|
|
| |
| response1 = agent_control_client.post("/api/agent/start", json={}) |
| assert response1.status_code == 200 |
|
|
| |
| mock_dm.is_running.return_value = True |
| response2 = agent_control_client.post("/api/agent/start", json={}) |
| assert response2.status_code == 400 |
|
|
| def test_very_long_host_name(self, agent_control_client: TestClient): |
| """Test start handles very long host names (1000+ characters).""" |
| with patch('api.agent_control_routes.DaemonManager') as mock_dm: |
| mock_dm.is_running.return_value = False |
| mock_dm.start_daemon.return_value = 12345 |
|
|
| long_host = "a" * 1000 |
| response = agent_control_client.post( |
| "/api/agent/start", |
| json={"host": long_host} |
| ) |
|
|
| |
| assert response.status_code == 200 |
|
|
| def test_special_characters_in_command(self, agent_control_client: TestClient): |
| """Test execute handles special characters in command.""" |
| special_command = "agent.chat('Hello\\nWorld\\t!'); system.run('rm -rf /')" |
| response = agent_control_client.post( |
| "/api/agent/execute", |
| json={"command": special_command} |
| ) |
|
|
| |
| assert response.status_code == 200 |
|
|
| def test_maximum_workers(self, agent_control_client: TestClient): |
| """Test start with 16 workers (maximum allowed).""" |
| with patch('api.agent_control_routes.DaemonManager') as mock_dm: |
| mock_dm.is_running.return_value = False |
| mock_dm.start_daemon.return_value = 12345 |
|
|
| response = agent_control_client.post( |
| "/api/agent/start", |
| json={"workers": 16} |
| ) |
|
|
| assert response.status_code == 200 |
|
|
| def test_minimum_workers(self, agent_control_client: TestClient): |
| """Test start with 1 worker (minimum allowed).""" |
| with patch('api.agent_control_routes.DaemonManager') as mock_dm: |
| mock_dm.is_running.return_value = False |
| mock_dm.start_daemon.return_value = 12345 |
|
|
| response = agent_control_client.post( |
| "/api/agent/start", |
| json={"workers": 1} |
| ) |
|
|
| assert response.status_code == 200 |
|
|
| def test_boundary_port_values(self, agent_control_client: TestClient): |
| """Test boundary port values (1, 65535).""" |
| with patch('api.agent_control_routes.DaemonManager') as mock_dm: |
| mock_dm.is_running.return_value = False |
| mock_dm.start_daemon.return_value = 12345 |
|
|
| |
| response1 = agent_control_client.post( |
| "/api/agent/start", |
| json={"port": 1} |
| ) |
| assert response1.status_code == 200 |
|
|
| |
| response2 = agent_control_client.post( |
| "/api/agent/start", |
| json={"port": 65535} |
| ) |
| assert response2.status_code == 200 |
|
|
|
|
| class TestAgentControlEdgeCases: |
| """Test edge cases for agent control routes.""" |
|
|
| def test_restart_loop_protection(self, agent_control_client: TestClient): |
| """Test restart has built-in delay to prevent restart loops.""" |
| import time |
|
|
| with patch('api.agent_control_routes.DaemonManager') as mock_dm: |
| mock_dm.is_running.return_value = True |
| mock_dm.start_daemon.return_value = 54321 |
|
|
| |
| with patch('time.sleep') as mock_sleep: |
| response = agent_control_client.post("/api/agent/restart", json={}) |
|
|
| |
| mock_sleep.assert_called_once_with(2) |
|
|
| assert response.status_code == 200 |
|
|
| def test_concurrent_stop_requests(self, agent_control_client: TestClient): |
| """Test multiple simultaneous stop requests (idempotent).""" |
| with patch('api.agent_control_routes.DaemonManager') as mock_dm: |
| mock_dm.is_running.return_value = True |
|
|
| |
| response1 = agent_control_client.post("/api/agent/stop", json={}) |
| assert response1.status_code == 200 |
|
|
| |
| mock_dm.is_running.return_value = False |
| response2 = agent_control_client.post("/api/agent/stop", json={}) |
| assert response2.status_code == 400 |
|
|
| def test_status_with_stale_pid_file(self, agent_control_client: TestClient): |
| """Test status handles stale PID file (process died).""" |
| test_status = { |
| "running": False, |
| "pid": 12345, |
| "uptime_seconds": None, |
| "memory_mb": None, |
| "cpu_percent": None, |
| "status": "stale_pid_file", |
| "note": "Stale PID file" |
| } |
|
|
| with patch('api.agent_control_routes.DaemonManager') as mock_dm: |
| mock_dm.get_status.return_value = test_status |
|
|
| response = agent_control_client.get("/api/agent/status") |
|
|
| assert response.status_code == 200 |
| data = response.json() |
| assert data["status"]["running"] is False |
| assert data["status"]["status"] == "stale_pid_file" |
|
|