"""Unit tests for PM3Service. Tests the PM3Service business logic layer, ensuring: - Proper session validation - Command execution flow - Error handling and error codes - Response format consistency """ import pytest from unittest.mock import AsyncMock, Mock from dataclasses import dataclass from app.backend.services.pm3_service import PM3Service, PM3ServiceResult, PM3ServiceError @dataclass class MockPM3Result: """Mock PM3Worker command result.""" success: bool output: str = "" error: str = None class TestPM3ServiceCommandExecution: """Test command execution with session validation.""" @pytest.mark.asyncio async def test_execute_command_success(self, mock_pm3_worker, mock_session_manager): """Test successful command execution.""" # Arrange mock_pm3_worker.execute_command.return_value = MockPM3Result( success=True, output="Proxmark3 RFID instrument" ) service = PM3Service(mock_pm3_worker, mock_session_manager) # Act result = await service.execute_command( command="hw version", session_id="test-session" ) # Assert assert result.success is True assert result.data is not None assert result.data["output"] == "Proxmark3 RFID instrument" assert result.data["command"] == "hw version" assert result.data["session_id"] == "test-session" assert result.error is None # Verify session was updated mock_session_manager.update_activity.assert_called_once_with("test-session") @pytest.mark.asyncio async def test_execute_command_session_locked(self, mock_pm3_worker, mock_session_manager): """Test command execution when another session is active.""" # Arrange mock_session_manager.can_execute.return_value = False service = PM3Service(mock_pm3_worker, mock_session_manager) # Act result = await service.execute_command( command="hw version", session_id="test-session" ) # Assert assert result.success is False assert result.error is not None assert result.error.code == "session_locked" assert "Another session is active" in result.error.message assert result.data is None # Verify command was not executed mock_pm3_worker.execute_command.assert_not_called() @pytest.mark.asyncio async def test_execute_command_pm3_not_connected(self, mock_pm3_worker, mock_session_manager): """Test command execution when PM3 is not connected.""" # Arrange mock_pm3_worker.is_connected.return_value = False service = PM3Service(mock_pm3_worker, mock_session_manager) # Act result = await service.execute_command( command="hw version", session_id="test-session" ) # Assert assert result.success is False assert result.error is not None assert result.error.code == "pm3_not_connected" assert "not connected" in result.error.message.lower() assert result.data is None # Verify command was not executed mock_pm3_worker.execute_command.assert_not_called() @pytest.mark.asyncio async def test_execute_command_failure(self, mock_pm3_worker, mock_session_manager): """Test command execution when PM3 command fails.""" # Arrange mock_pm3_worker.execute_command.return_value = MockPM3Result( success=False, error="Invalid command" ) service = PM3Service(mock_pm3_worker, mock_session_manager) # Act result = await service.execute_command( command="invalid", session_id="test-session" ) # Assert assert result.success is False assert result.error is not None assert result.error.code == "command_failed" assert "Invalid command" in result.error.details @pytest.mark.asyncio async def test_execute_command_exception_handling(self, mock_pm3_worker, mock_session_manager): """Test command execution when an exception occurs.""" # Arrange mock_pm3_worker.execute_command.side_effect = Exception("Connection lost") service = PM3Service(mock_pm3_worker, mock_session_manager) # Act result = await service.execute_command( command="hw version", session_id="test-session" ) # Assert assert result.success is False assert result.error is not None assert result.error.code == "execution_error" assert "Connection lost" in result.error.details class TestPM3ServiceStatus: """Test status query operations.""" @pytest.mark.asyncio async def test_get_status_connected(self, mock_pm3_worker, mock_session_manager): """Test status query when PM3 is connected.""" # Arrange mock_pm3_worker.execute_command.return_value = MockPM3Result( success=True, output="Proxmark3 RFID instrument\nFirmware version: v4.0" ) mock_session_manager.has_active_session.return_value = True service = PM3Service(mock_pm3_worker, mock_session_manager) # Act result = await service.get_status() # Assert assert result.success is True assert result.data["connected"] is True assert result.data["device_path"] == "/dev/ttyACM0" assert "Proxmark3" in result.data["version"] assert result.data["session_active"] is True @pytest.mark.asyncio async def test_get_status_not_connected(self, mock_pm3_worker, mock_session_manager): """Test status query when PM3 is not connected.""" # Arrange mock_pm3_worker.is_connected.return_value = False service = PM3Service(mock_pm3_worker, mock_session_manager) # Act result = await service.get_status() # Assert assert result.success is True assert result.data["connected"] is False assert result.data["version"] is None class TestPM3ServiceConnection: """Test connection management.""" @pytest.mark.asyncio async def test_connect_success(self, mock_pm3_worker, mock_session_manager): """Test successful PM3 connection.""" # Arrange service = PM3Service(mock_pm3_worker, mock_session_manager) # Act result = await service.connect() # Assert assert result.success is True assert "Connected" in result.data["message"] mock_pm3_worker.connect.assert_called_once() @pytest.mark.asyncio async def test_connect_failure(self, mock_pm3_worker, mock_session_manager): """Test PM3 connection failure.""" # Arrange mock_pm3_worker.connect.side_effect = Exception("Device not found") service = PM3Service(mock_pm3_worker, mock_session_manager) # Act result = await service.connect() # Assert assert result.success is False assert result.error.code == "connection_error" assert "Device not found" in result.error.details @pytest.mark.asyncio async def test_disconnect_success(self, mock_pm3_worker, mock_session_manager): """Test successful PM3 disconnection.""" # Arrange service = PM3Service(mock_pm3_worker, mock_session_manager) # Act result = await service.disconnect() # Assert assert result.success is True assert "Disconnected" in result.data["message"] mock_pm3_worker.disconnect.assert_called_once() class TestPM3ServiceSessionManagement: """Test session management operations.""" def test_create_session_success(self, mock_pm3_worker, mock_session_manager): """Test successful session creation.""" # Arrange service = PM3Service(mock_pm3_worker, mock_session_manager) # Act result = service.create_session(force_takeover=False) # Assert assert result.success is True assert result.data["session_id"] == "test-session-id" mock_session_manager.create_session.assert_called_once_with(force_takeover=False) def test_create_session_exists(self, mock_pm3_worker, mock_session_manager): """Test session creation when session already exists.""" # Arrange mock_session_manager.create_session.return_value = None service = PM3Service(mock_pm3_worker, mock_session_manager) # Act result = service.create_session(force_takeover=False) # Assert assert result.success is False assert result.error.code == "session_exists" assert "force_takeover" in result.error.details.lower() def test_release_session(self, mock_pm3_worker, mock_session_manager): """Test session release.""" # Arrange service = PM3Service(mock_pm3_worker, mock_session_manager) # Act result = service.release_session("test-session") # Assert assert result.success is True assert "released" in result.data["message"].lower() mock_session_manager.release_session.assert_called_once_with("test-session") def test_get_session_info_found(self, mock_pm3_worker, mock_session_manager): """Test getting session info for existing session.""" # Arrange from datetime import datetime mock_session = Mock() mock_session.session_id = "test-session" mock_session.created_at = datetime(2025, 1, 1, 12, 0, 0) mock_session.last_activity = datetime(2025, 1, 1, 12, 5, 0) mock_session_manager.get_session.return_value = mock_session mock_session_manager.is_session_active.return_value = True service = PM3Service(mock_pm3_worker, mock_session_manager) # Act result = service.get_session_info("test-session") # Assert assert result.success is True assert result.data["session_id"] == "test-session" assert result.data["is_active"] is True def test_get_session_info_not_found(self, mock_pm3_worker, mock_session_manager): """Test getting session info for non-existent session.""" # Arrange mock_session_manager.get_session.return_value = None service = PM3Service(mock_pm3_worker, mock_session_manager) # Act result = service.get_session_info("nonexistent") # Assert assert result.success is False assert result.error.code == "session_not_found"