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:
michael
2026-01-06 13:45:29 -08:00
parent 1da6730735
commit 4f35df1781
323 changed files with 98287 additions and 1195 deletions

View File

@@ -0,0 +1,210 @@
"""Integration tests for multi-device discovery."""
import pytest
import asyncio
from unittest.mock import Mock, patch
from app.backend.managers.pm3_device_manager import (
PM3DeviceManager,
DeviceStatus
)
from app.backend.managers.session_manager import SessionManager
@pytest.fixture
async def device_manager():
"""Create and start device manager."""
manager = PM3DeviceManager()
# Don't start monitoring for tests
yield manager
await manager.stop()
@pytest.fixture
def session_manager():
"""Create session manager."""
return SessionManager()
class TestMultiDeviceDiscovery:
"""Integration tests for device discovery."""
@pytest.mark.asyncio
async def test_discover_no_devices(self, device_manager):
"""Test discovery when no devices are connected."""
with patch('app.backend.managers.pm3_device_manager.SERIAL_AVAILABLE', False):
with patch('pathlib.Path.glob', return_value=[]):
devices = await device_manager.discover_devices()
assert len(devices) == 0
@pytest.mark.asyncio
async def test_discover_single_device(self, device_manager):
"""Test discovering a single device."""
with patch('pathlib.Path.glob') as mock_glob:
# Mock single device
mock_device = Mock()
mock_device.is_char_device.return_value = True
mock_device.__str__ = lambda x: "/dev/ttyACM0"
mock_glob.return_value = [mock_device]
devices = await device_manager.discover_devices()
assert len(devices) == 1
assert devices[0].device_path == "/dev/ttyACM0"
assert devices[0].status == DeviceStatus.CONNECTED
@pytest.mark.asyncio
async def test_discover_multiple_devices(self, device_manager):
"""Test discovering multiple devices."""
with patch('pathlib.Path.glob') as mock_glob:
# Mock multiple devices
mock_devices = []
for i in range(3):
mock_device = Mock()
mock_device.is_char_device.return_value = True
mock_device.__str__ = lambda x, i=i: f"/dev/ttyACM{i}"
mock_devices.append(mock_device)
mock_glob.return_value = mock_devices
devices = await device_manager.discover_devices()
assert len(devices) == 3
paths = [d.device_path for d in devices]
assert "/dev/ttyACM0" in paths
assert "/dev/ttyACM1" in paths
assert "/dev/ttyACM2" in paths
@pytest.mark.asyncio
async def test_device_hotplug_simulation(self, device_manager):
"""Test device hotplug by simulating device connect/disconnect."""
with patch('pathlib.Path.glob') as mock_glob:
# First discovery: 1 device
mock_device1 = Mock()
mock_device1.is_char_device.return_value = True
mock_device1.__str__ = lambda x: "/dev/ttyACM0"
mock_glob.return_value = [mock_device1]
devices = await device_manager.discover_devices()
assert len(devices) == 1
device1_id = devices[0].device_id
# Second discovery: 2 devices (device plugged in)
mock_device2 = Mock()
mock_device2.is_char_device.return_value = True
mock_device2.__str__ = lambda x: "/dev/ttyACM1"
mock_glob.return_value = [mock_device1, mock_device2]
devices = await device_manager.discover_devices()
assert len(devices) == 2
# Third discovery: 1 device (device unplugged)
mock_glob.return_value = [mock_device1]
devices = await device_manager.discover_devices()
# Still 2 devices tracked, but one is DISCONNECTED
assert len(devices) == 2
connected = [d for d in devices if d.status == DeviceStatus.CONNECTED]
disconnected = [d for d in devices if d.status == DeviceStatus.DISCONNECTED]
assert len(connected) == 1
assert len(disconnected) == 1
@pytest.mark.asyncio
async def test_device_persistence_across_discovery(self, device_manager):
"""Test that device objects persist across discovery runs."""
with patch('pathlib.Path.glob') as mock_glob:
mock_device = Mock()
mock_device.is_char_device.return_value = True
mock_device.__str__ = lambda x: "/dev/ttyACM0"
mock_glob.return_value = [mock_device]
# First discovery
devices1 = await device_manager.discover_devices()
device_id = devices1[0].device_id
# Second discovery
devices2 = await device_manager.discover_devices()
# Device ID should be the same
assert len(devices2) == 1
assert devices2[0].device_id == device_id
class TestMultiDeviceSessionIsolation:
"""Integration tests for session isolation across devices."""
@pytest.mark.asyncio
async def test_single_session_per_device(self, device_manager, session_manager):
"""Test that each device can have its own session."""
# Note: This will be fully implemented in Sprint 2
# For now, just verify session manager exists
assert session_manager is not None
# TODO (Sprint 2): Test multiple devices with independent sessions
class TestDeviceFiltering:
"""Integration tests for device filtering."""
@pytest.mark.asyncio
async def test_filter_by_status(self, device_manager):
"""Test filtering devices by status."""
with patch('pathlib.Path.glob') as mock_glob:
mock_devices = []
for i in range(3):
mock_device = Mock()
mock_device.is_char_device.return_value = True
mock_device.__str__ = lambda x, i=i: f"/dev/ttyACM{i}"
mock_devices.append(mock_device)
mock_glob.return_value = mock_devices
# Discover devices
await device_manager.discover_devices()
# Set different statuses
devices = await device_manager.get_all_devices()
await device_manager.set_device_status(devices[0].device_id, DeviceStatus.CONNECTED)
await device_manager.set_device_status(devices[1].device_id, DeviceStatus.IN_USE)
await device_manager.set_device_status(devices[2].device_id, DeviceStatus.DISCONNECTED)
# Get available devices (only CONNECTED)
available = await device_manager.get_available_devices()
assert len(available) == 1
assert available[0].status == DeviceStatus.CONNECTED
# Get connected devices (CONNECTED + IN_USE)
connected = await device_manager.get_connected_devices()
assert len(connected) == 2
@pytest.mark.hardware
@pytest.mark.asyncio
async def test_real_hardware_discovery():
"""Test discovery with real hardware.
This test requires actual Proxmark3 hardware connected.
Run with: pytest -m hardware
"""
manager = PM3DeviceManager()
try:
devices = await manager.discover_devices()
# If hardware is connected, verify it was found
if devices:
print(f"\nFound {len(devices)} PM3 device(s):")
for device in devices:
print(f" - {device.device_path} (ID: {device.device_id})")
print(f" Status: {device.status.value}")
print(f" VID:PID: {device.usb_vid}:{device.usb_pid}")
else:
print("\nNo PM3 devices found (this is OK for CI/CD)")
finally:
await manager.stop()
if __name__ == "__main__":
pytest.main([__file__, "-v"])