| """ |
| Coverage expansion tests for device automation tool. |
| |
| Tests cover critical code paths in: |
| - tools/device_tool.py: Device session management, WebSocket communication |
| - Camera capture, screen recording, location services |
| - Governance enforcement for device operations |
| - Command execution with whitelist enforcement |
| |
| Target: Cover critical paths (happy path + error paths) to increase coverage. |
| Uses extensive mocking to avoid device/websocket dependencies. |
| """ |
|
|
| import pytest |
| from unittest.mock import Mock, patch, MagicMock, AsyncMock |
| from datetime import datetime |
| import os |
|
|
| from tools.device_tool import DeviceSessionManager |
|
|
|
|
| class TestDeviceSessionManagerCoverage: |
| """Coverage expansion for DeviceSessionManager class.""" |
|
|
| @pytest.fixture |
| def session_manager(self): |
| """Get session manager instance.""" |
| return DeviceSessionManager(session_timeout_minutes=60) |
|
|
| |
| def test_session_manager_init(self, session_manager): |
| """Session manager initializes correctly.""" |
| assert session_manager.sessions == {} |
| assert session_manager.session_timeout_minutes == 60 |
|
|
| def test_session_manager_custom_timeout(self): |
| """Session manager with custom timeout.""" |
| manager = DeviceSessionManager(session_timeout_minutes=30) |
| assert manager.session_timeout_minutes == 30 |
|
|
| |
| def test_create_session_success(self, session_manager): |
| """Successfully create device session.""" |
| result = session_manager.create_session( |
| user_id="user-123", |
| device_node_id="device-123", |
| session_type="camera" |
| ) |
|
|
| assert "session_id" in result |
| assert result["user_id"] == "user-123" |
| assert result["device_node_id"] == "device-123" |
| assert result["session_type"] == "camera" |
| assert result["session_id"] in session_manager.sessions |
|
|
| def test_create_session_with_agent(self, session_manager): |
| """Create session with agent ID.""" |
| result = session_manager.create_session( |
| user_id="user-123", |
| device_node_id="device-123", |
| session_type="location", |
| agent_id="agent-123" |
| ) |
|
|
| assert result["agent_id"] == "agent-123" |
|
|
| def test_create_session_with_configuration(self, session_manager): |
| """Create session with configuration.""" |
| config = {"resolution": "1080p", "fps": 30} |
| result = session_manager.create_session( |
| user_id="user-123", |
| device_node_id="device-123", |
| session_type="screen_record", |
| configuration=config |
| ) |
|
|
| assert result["configuration"] == config |
|
|
| |
| def test_get_session_success(self, session_manager): |
| """Successfully retrieve existing session.""" |
| created = session_manager.create_session( |
| user_id="user-123", |
| device_node_id="device-123", |
| session_type="camera" |
| ) |
|
|
| retrieved = session_manager.get_session(created["session_id"]) |
| assert retrieved is not None |
| assert retrieved["session_id"] == created["session_id"] |
|
|
| def test_get_session_not_found(self, session_manager): |
| """Get non-existent session returns None.""" |
| result = session_manager.get_session("nonexistent") |
| assert result is None |
|
|
| |
| def test_delete_session_success(self, session_manager): |
| """Successfully delete session.""" |
| created = session_manager.create_session( |
| user_id="user-123", |
| device_node_id="device-123", |
| session_type="camera" |
| ) |
|
|
| session_manager.delete_session(created["session_id"]) |
| assert session_manager.get_session(created["session_id"]) is None |
|
|
| def test_delete_session_not_found(self, session_manager): |
| """Delete non-existent session doesn't raise error.""" |
| session_manager.delete_session("nonexistent") |
| |
|
|
|
|
| class TestDeviceToolCoverage: |
| """Coverage expansion for DeviceTool class.""" |
|
|
| @pytest.fixture |
| def mock_db_session(self): |
| """Get mock database session.""" |
| return MagicMock() |
|
|
| @pytest.fixture |
| def device_tool(self, mock_db_session): |
| """Get device tool instance.""" |
| return DeviceTool(mock_db_session) |
|
|
| |
| def test_device_tool_init(self, device_tool): |
| """Device tool initializes correctly.""" |
| assert device_tool.db == device_tool.db |
| assert isinstance(device_tool.session_manager, DeviceSessionManager) |
|
|
| |
| @patch('tools.device_tool.send_device_command') |
| @pytest.mark.asyncio |
| async def test_camera_capture_snap_success(self, mock_send_cmd, device_tool): |
| """Successfully capture camera snap.""" |
| mock_send_cmd.return_value = { |
| "success": True, |
| "data": "base64_image_data" |
| } |
|
|
| result = await device_tool.camera_snap( |
| device_node_id="device-123", |
| user_id="user-123" |
| ) |
|
|
| assert result["success"] == True |
| mock_send_cmd.assert_called_once() |
|
|
| @patch('tools.device_tool.send_device_command') |
| @pytest.mark.asyncio |
| async def test_camera_capture_clip_success(self, mock_send_cmd, device_tool): |
| """Successfully capture camera clip.""" |
| mock_send_cmd.return_value = { |
| "success": True, |
| "data": "base64_video_data" |
| } |
|
|
| result = await device_tool.camera_clip( |
| device_node_id="device-123", |
| user_id="user-123", |
| duration_ms=5000 |
| ) |
|
|
| assert result["success"] == True |
|
|
| |
| @patch('tools.device_tool.send_device_command') |
| @pytest.mark.asyncio |
| async def test_get_location_success(self, mock_send_cmd, device_tool): |
| """Successfully get device location.""" |
| mock_send_cmd.return_value = { |
| "success": True, |
| "latitude": 37.7749, |
| "longitude": -122.4194, |
| "accuracy": 10.0 |
| } |
|
|
| result = await device_tool.get_location( |
| device_node_id="device-123", |
| user_id="user-123" |
| ) |
|
|
| assert result["success"] == True |
| assert "latitude" in result |
|
|
| |
| @patch('tools.device_tool.send_device_command') |
| @pytest.mark.asyncio |
| async def test_start_screen_recording_success(self, mock_send_cmd, device_tool): |
| """Successfully start screen recording.""" |
| mock_send_cmd.return_value = { |
| "success": True, |
| "session_id": "recording-session-123" |
| } |
|
|
| result = await device_tool.start_screen_recording( |
| device_node_id="device-123", |
| user_id="user-123" |
| ) |
|
|
| assert result["success"] == True |
| assert "session_id" in result |
|
|
| @patch('tools.device_tool.send_device_command') |
| @pytest.mark.asyncio |
| async def test_stop_screen_recording_success(self, mock_send_cmd, device_tool): |
| """Successfully stop screen recording.""" |
| mock_send_cmd.return_value = { |
| "success": True, |
| "data": "base64_video_data" |
| } |
|
|
| result = await device_tool.stop_screen_recording( |
| device_node_id="device-123", |
| user_id="user-123", |
| recording_session_id="rec-123" |
| ) |
|
|
| assert result["success"] == True |
|
|
| |
| @patch('tools.device_tool.send_device_command') |
| @pytest.mark.asyncio |
| async def test_send_notification_success(self, mock_send_cmd, device_tool): |
| """Successfully send notification.""" |
| mock_send_cmd.return_value = { |
| "success": True |
| } |
|
|
| result = await device_tool.send_notification( |
| device_node_id="device-123", |
| user_id="user-123", |
| title="Test Notification", |
| body="Test body", |
| payload={"key": "value"} |
| ) |
|
|
| assert result["success"] == True |
|
|
| |
| @patch('tools.device_tool.send_device_command') |
| @pytest.mark.asyncio |
| async def test_execute_command_success(self, mock_send_cmd, device_tool): |
| """Successfully execute whitelisted command.""" |
| mock_send_cmd.return_value = { |
| "success": True, |
| "output": "file1.txt\nfile2.txt" |
| } |
|
|
| result = await device_tool.execute_command( |
| device_node_id="device-123", |
| user_id="user-123", |
| command="ls", |
| args=["-la"] |
| ) |
|
|
| assert result["success"] == True |
| assert "output" in result |
|
|
| @pytest.mark.asyncio |
| async def test_execute_command_not_whitelisted(self, device_tool): |
| """Reject command not in whitelist.""" |
| |
| result = await device_tool.execute_command( |
| device_node_id="device-123", |
| user_id="user-123", |
| command="rm", |
| args=["-rf", "/"] |
| ) |
|
|
| assert result["success"] == False |
| assert "whitelist" in result["error"].lower() or "not allowed" in result["error"].lower() |
|
|
|
|
| class TestDeviceToolErrorHandling: |
| """Coverage expansion for device tool error handling.""" |
|
|
| @pytest.fixture |
| def mock_db_session(self): |
| """Get mock database session.""" |
| return MagicMock() |
|
|
| @pytest.fixture |
| def device_tool(self, mock_db_session): |
| """Get device tool instance.""" |
| return DeviceTool(mock_db_session) |
|
|
| |
| @patch('tools.device_tool.send_device_command') |
| @pytest.mark.asyncio |
| async def test_camera_capture_device_offline(self, mock_send_cmd, device_tool): |
| """Camera capture fails when device offline.""" |
| mock_send_cmd.return_value = { |
| "success": False, |
| "error": "Device not connected" |
| } |
|
|
| result = await device_tool.camera_snap( |
| device_node_id="offline-device", |
| user_id="user-123" |
| ) |
|
|
| assert result["success"] == False |
|
|
| |
| @patch('tools.device_tool.send_device_command') |
| @pytest.mark.asyncio |
| async def test_execute_command_timeout(self, mock_send_cmd, device_tool): |
| """Command execution times out.""" |
| import asyncio |
| mock_send_cmd.side_effect = asyncio.TimeoutError("Command timeout") |
|
|
| result = await device_tool.execute_command( |
| device_node_id="device-123", |
| user_id="user-123", |
| command="sleep", |
| args=["100"] |
| ) |
|
|
| assert result["success"] == False |
|
|
| |
| @pytest.mark.asyncio |
| async def test_screen_recording_exceeds_max_duration(self, device_tool): |
| """Screen recording exceeds max duration limit.""" |
| max_duration = int(os.getenv("DEVICE_SCREEN_RECORD_MAX_DURATION", "3600")) |
|
|
| result = await device_tool.start_screen_recording( |
| device_node_id="device-123", |
| user_id="user-123", |
| duration_seconds=max_duration + 1000 |
| ) |
|
|
| assert result["success"] == False |
| assert "duration" in result["error"].lower() |
|
|
| |
| @pytest.mark.asyncio |
| async def test_camera_snap_missing_device_id(self, device_tool): |
| """Camera snap without device ID.""" |
| result = await device_tool.camera_snap( |
| device_node_id="", |
| user_id="user-123" |
| ) |
|
|
| assert result["success"] == False |
|
|
| @pytest.mark.asyncio |
| async def test_send_notification_missing_title(self, device_tool): |
| """Send notification without title.""" |
| result = await device_tool.send_notification( |
| device_node_id="device-123", |
| user_id="user-123", |
| title="", |
| body="Body text" |
| ) |
|
|
| assert result["success"] == False |
|
|