Initial commit - Phase 3/4
🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
305
tests/unit/services/test_pm3_service.py
Normal file
305
tests/unit/services/test_pm3_service.py
Normal file
@@ -0,0 +1,305 @@
|
||||
"""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"
|
||||
Reference in New Issue
Block a user