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

View 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"

View File

@@ -0,0 +1,300 @@
"""Unit tests for SystemService.
Tests the SystemService business logic layer, ensuring:
- System information queries
- Shutdown/restart operations
- Error handling and error codes
- Command execution and output parsing
"""
import pytest
from unittest.mock import AsyncMock, Mock, patch
from app.backend.services.system_service import SystemService
class TestSystemServiceInfo:
"""Test system information queries."""
@pytest.mark.asyncio
async def test_get_info_success(self):
"""Test successful system info retrieval."""
# Arrange
service = SystemService()
# Act
with patch('psutil.cpu_percent', return_value=25.5), \
patch('psutil.virtual_memory') as mock_memory, \
patch('psutil.disk_usage') as mock_disk, \
patch('psutil.cpu_count', return_value=4), \
patch('psutil.boot_time', return_value=1000000000.0), \
patch('psutil.time.time', return_value=1000086400.0), \
patch('platform.node', return_value='dangerous-pi'), \
patch('platform.system', return_value='Linux'), \
patch('platform.release', return_value='6.1.21'):
# Mock memory stats
mock_memory.return_value = Mock(
total=4294967296, # 4GB
used=2147483648, # 2GB
percent=50.0
)
# Mock disk stats
mock_disk.return_value = Mock(
total=32212254720, # 30GB
used=10737418240, # 10GB
percent=33.3
)
result = await service.get_info()
# Assert
assert result.success is True
assert result.data["hostname"] == "dangerous-pi"
assert result.data["platform"] == "Linux"
assert result.data["cpu"]["count"] == 4
assert result.data["cpu"]["percent"] == 25.5
assert result.data["memory"]["total"] == 4294967296
assert result.data["memory"]["used"] == 2147483648
assert result.data["memory"]["percent"] == 50.0
assert result.data["disk"]["total"] == 32212254720
assert result.data["uptime"] == 86400.0 # 1 day
@pytest.mark.asyncio
async def test_get_info_with_temperature(self):
"""Test system info with CPU temperature."""
# Arrange
service = SystemService()
# Act
with patch('psutil.cpu_percent', return_value=50.0), \
patch('psutil.virtual_memory'), \
patch('psutil.disk_usage'), \
patch('psutil.cpu_count', return_value=4), \
patch('psutil.boot_time', return_value=1000000000.0), \
patch('psutil.time.time', return_value=1000000100.0), \
patch('platform.node', return_value='test'), \
patch('platform.system', return_value='Linux'), \
patch('platform.release', return_value='6.1'), \
patch('pathlib.Path.exists', return_value=True), \
patch('pathlib.Path.read_text', return_value='45678'):
# Mock memory and disk
patch('psutil.virtual_memory', return_value=Mock(total=1, used=1, percent=1))
patch('psutil.disk_usage', return_value=Mock(total=1, used=1, percent=1))
result = await service.get_info()
# Assert
assert result.success is True
assert result.data["cpu"]["temperature"] == 45.678
@pytest.mark.asyncio
async def test_get_info_exception_handling(self):
"""Test system info retrieval with exception."""
# Arrange
service = SystemService()
# Act
with patch('psutil.cpu_percent', side_effect=Exception("psutil error")):
result = await service.get_info()
# Assert
assert result.success is False
assert result.error.code == "system_info_error"
assert "psutil error" in result.error.details
class TestSystemServiceShutdown:
"""Test shutdown operations."""
@pytest.mark.asyncio
async def test_shutdown_immediate(self):
"""Test immediate shutdown."""
# Arrange
service = SystemService()
# Act
with patch.object(service, '_run_command', new_callable=AsyncMock) as mock_cmd:
mock_cmd.return_value = ""
result = await service.shutdown(delay=0)
# Assert
assert result.success is True
assert "initiated" in result.data["message"].lower()
mock_cmd.assert_called_once_with("sudo shutdown -h now")
@pytest.mark.asyncio
async def test_shutdown_with_delay(self):
"""Test scheduled shutdown."""
# Arrange
service = SystemService()
# Act
with patch.object(service, '_run_command', new_callable=AsyncMock) as mock_cmd:
mock_cmd.return_value = ""
result = await service.shutdown(delay=300) # 5 minutes
# Assert
assert result.success is True
assert "scheduled" in result.data["message"].lower()
assert result.data["delay"] == 300
# 300 seconds = 5 minutes
mock_cmd.assert_called_once_with("sudo shutdown -h +5")
@pytest.mark.asyncio
async def test_shutdown_failure(self):
"""Test shutdown command failure."""
# Arrange
service = SystemService()
# Act
with patch.object(service, '_run_command', new_callable=AsyncMock) as mock_cmd:
mock_cmd.side_effect = RuntimeError("Permission denied")
result = await service.shutdown(delay=0)
# Assert
assert result.success is False
assert result.error.code == "shutdown_error"
assert "Permission denied" in result.error.details
class TestSystemServiceRestart:
"""Test restart operations."""
@pytest.mark.asyncio
async def test_restart_immediate(self):
"""Test immediate restart."""
# Arrange
service = SystemService()
# Act
with patch.object(service, '_run_command', new_callable=AsyncMock) as mock_cmd:
mock_cmd.return_value = ""
result = await service.restart(delay=0)
# Assert
assert result.success is True
assert "initiated" in result.data["message"].lower()
mock_cmd.assert_called_once_with("sudo shutdown -r now")
@pytest.mark.asyncio
async def test_restart_with_delay(self):
"""Test scheduled restart."""
# Arrange
service = SystemService()
# Act
with patch.object(service, '_run_command', new_callable=AsyncMock) as mock_cmd:
mock_cmd.return_value = ""
result = await service.restart(delay=600) # 10 minutes
# Assert
assert result.success is True
assert "scheduled" in result.data["message"].lower()
assert result.data["delay"] == 600
mock_cmd.assert_called_once_with("sudo shutdown -r +10")
class TestSystemServiceCancelShutdown:
"""Test shutdown cancellation."""
@pytest.mark.asyncio
async def test_cancel_shutdown_success(self):
"""Test successful shutdown cancellation."""
# Arrange
service = SystemService()
# Act
with patch.object(service, '_run_command', new_callable=AsyncMock) as mock_cmd:
mock_cmd.return_value = ""
result = await service.cancel_shutdown()
# Assert
assert result.success is True
assert "cancelled" in result.data["message"].lower()
mock_cmd.assert_called_once_with("sudo shutdown -c")
class TestSystemServiceLogs:
"""Test log retrieval."""
@pytest.mark.asyncio
async def test_get_logs_success(self):
"""Test successful log retrieval."""
# Arrange
service = SystemService()
log_content = "Jan 01 12:00:00 test systemd[1]: Started dangerous-pi"
# Act
with patch.object(service, '_run_command', new_callable=AsyncMock) as mock_cmd:
mock_cmd.return_value = log_content
result = await service.get_logs(service="dangerous-pi", lines=100)
# Assert
assert result.success is True
assert result.data["service"] == "dangerous-pi"
assert result.data["lines"] == 100
assert result.data["logs"] == log_content
mock_cmd.assert_called_once_with(
"sudo journalctl -u dangerous-pi -n 100 --no-pager"
)
@pytest.mark.asyncio
async def test_get_logs_failure(self):
"""Test log retrieval failure."""
# Arrange
service = SystemService()
# Act
with patch.object(service, '_run_command', new_callable=AsyncMock) as mock_cmd:
mock_cmd.side_effect = RuntimeError("Service not found")
result = await service.get_logs(service="nonexistent")
# Assert
assert result.success is False
assert result.error.code == "logs_error"
class TestSystemServiceStatus:
"""Test service status queries."""
@pytest.mark.asyncio
async def test_get_service_status_active(self):
"""Test getting status of active service."""
# Arrange
service = SystemService()
status_output = "● dangerous-pi.service - Dangerous Pi Backend\n Loaded: loaded\n Active: active (running)"
# Act
with patch.object(service, '_run_command', new_callable=AsyncMock) as mock_cmd, \
patch.object(service, '_is_service_enabled', new_callable=AsyncMock) as mock_enabled:
mock_cmd.return_value = status_output
mock_enabled.return_value = True
result = await service.get_service_status("dangerous-pi")
# Assert
assert result.success is True
assert result.data["service"] == "dangerous-pi"
assert result.data["active"] is True
assert result.data["enabled"] is True
assert "active (running)" in result.data["status"]
@pytest.mark.asyncio
async def test_get_service_status_inactive(self):
"""Test getting status of inactive service."""
# Arrange
service = SystemService()
status_output = "● test.service\n Loaded: loaded\n Active: inactive (dead)"
# Act
with patch.object(service, '_run_command', new_callable=AsyncMock) as mock_cmd, \
patch.object(service, '_is_service_enabled', new_callable=AsyncMock) as mock_enabled:
mock_cmd.return_value = status_output
mock_enabled.return_value = False
result = await service.get_service_status("test")
# Assert
assert result.success is True
assert result.data["active"] is False
assert result.data["enabled"] is False

View File

@@ -0,0 +1,369 @@
"""Unit tests for UpdateService.
Tests the UpdateService business logic layer, ensuring:
- Update checking
- Update downloading
- Update installation
- Progress monitoring
- Release notes retrieval
- Error handling and error codes
"""
import pytest
from unittest.mock import AsyncMock, Mock
from app.backend.services.update_service import UpdateService
from app.backend.managers.update_manager import UpdateProgress, UpdateStatus
class TestUpdateServiceCheckForUpdates:
"""Test update checking operations."""
@pytest.mark.asyncio
async def test_check_for_updates_available(self, mock_update_manager):
"""Test checking for updates when update is available."""
# Arrange
mock_update_manager.check_for_updates.return_value = {
"update_available": True,
"current_version": "1.0.0",
"latest_version": "1.1.0",
"release_date": "2025-01-15",
"changelog": "## New Features\n- Feature 1\n- Feature 2",
"is_prerelease": False,
"download_size": 10485760 # 10MB
}
service = UpdateService(mock_update_manager)
# Act
result = await service.check_for_updates()
# Assert
assert result.success is True
assert result.data["update_available"] is True
assert result.data["latest_version"] == "1.1.0"
assert result.data["download_size"] == 10485760
@pytest.mark.asyncio
async def test_check_for_updates_none_available(self, mock_update_manager):
"""Test checking for updates when system is up to date."""
# Arrange
mock_update_manager.check_for_updates.return_value = {
"update_available": False,
"current_version": "1.1.0",
"latest_version": "1.1.0",
"message": "System is up to date"
}
service = UpdateService(mock_update_manager)
# Act
result = await service.check_for_updates()
# Assert
assert result.success is True
assert result.data["update_available"] is False
assert result.data["message"] == "System is up to date"
@pytest.mark.asyncio
async def test_check_for_updates_exception(self, mock_update_manager):
"""Test update check with exception."""
# Arrange
mock_update_manager.check_for_updates.side_effect = Exception("Network error")
service = UpdateService(mock_update_manager)
# Act
result = await service.check_for_updates()
# Assert
assert result.success is False
assert result.error.code == "update_check_error"
assert "Network error" in result.error.details
class TestUpdateServiceDownload:
"""Test update download operations."""
@pytest.mark.asyncio
async def test_download_update_success(self, mock_update_manager):
"""Test successful update download."""
# Arrange
mock_update_manager.download_update.return_value = True
service = UpdateService(mock_update_manager)
# Act
result = await service.download_update()
# Assert
assert result.success is True
assert "successfully" in result.data["message"].lower()
assert result.data["ready_to_install"] is True
@pytest.mark.asyncio
async def test_download_update_no_update_available(self, mock_update_manager):
"""Test download when no update is available."""
# Arrange
mock_update_manager.download_update.side_effect = ValueError("No update available to download")
service = UpdateService(mock_update_manager)
# Act
result = await service.download_update()
# Assert
assert result.success is False
assert result.error.code == "no_update_available"
@pytest.mark.asyncio
async def test_download_update_failure(self, mock_update_manager):
"""Test download failure."""
# Arrange
mock_update_manager.download_update.side_effect = Exception("Download failed")
service = UpdateService(mock_update_manager)
# Act
result = await service.download_update()
# Assert
assert result.success is False
assert result.error.code == "update_download_error"
class TestUpdateServiceInstall:
"""Test update installation operations."""
@pytest.mark.asyncio
async def test_install_update_success(self, mock_update_manager):
"""Test successful update installation."""
# Arrange
mock_update_manager.install_update.return_value = True
service = UpdateService(mock_update_manager)
# Act
result = await service.install_update()
# Assert
assert result.success is True
assert "successfully" in result.data["message"].lower()
assert result.data["requires_restart"] is True
@pytest.mark.asyncio
async def test_install_update_no_download(self, mock_update_manager):
"""Test install when no update has been downloaded."""
# Arrange
mock_update_manager.install_update.side_effect = ValueError("No update downloaded")
service = UpdateService(mock_update_manager)
# Act
result = await service.install_update()
# Assert
assert result.success is False
assert result.error.code == "no_update_downloaded"
@pytest.mark.asyncio
async def test_install_update_failure(self, mock_update_manager):
"""Test installation failure."""
# Arrange
mock_update_manager.install_update.side_effect = Exception("Installation failed")
service = UpdateService(mock_update_manager)
# Act
result = await service.install_update()
# Assert
assert result.success is False
assert result.error.code == "update_install_error"
class TestUpdateServiceProgress:
"""Test progress monitoring."""
@pytest.mark.asyncio
async def test_get_progress_idle(self, mock_update_manager):
"""Test getting progress when idle."""
# Arrange
mock_update_manager.get_progress.return_value = UpdateProgress(
status=UpdateStatus.IDLE,
current_version="1.0.0",
available_version=None,
download_progress=0.0,
error_message=None,
last_check="2025-01-15T10:00:00"
)
service = UpdateService(mock_update_manager)
# Act
result = await service.get_progress()
# Assert
assert result.success is True
assert result.data["status"] == "idle"
assert result.data["current_version"] == "1.0.0"
assert result.data["download_progress"] == 0.0
@pytest.mark.asyncio
async def test_get_progress_downloading(self, mock_update_manager):
"""Test getting progress during download."""
# Arrange
mock_update_manager.get_progress.return_value = UpdateProgress(
status=UpdateStatus.DOWNLOADING,
current_version="1.0.0",
available_version="1.1.0",
download_progress=45.5,
error_message=None,
last_check="2025-01-15T10:00:00"
)
service = UpdateService(mock_update_manager)
# Act
result = await service.get_progress()
# Assert
assert result.success is True
assert result.data["status"] == "downloading"
assert result.data["available_version"] == "1.1.0"
assert result.data["download_progress"] == 45.5
@pytest.mark.asyncio
async def test_get_progress_failed(self, mock_update_manager):
"""Test getting progress after failure."""
# Arrange
mock_update_manager.get_progress.return_value = UpdateProgress(
status=UpdateStatus.FAILED,
current_version="1.0.0",
available_version="1.1.0",
download_progress=0.0,
error_message="Network timeout",
last_check="2025-01-15T10:00:00"
)
service = UpdateService(mock_update_manager)
# Act
result = await service.get_progress()
# Assert
assert result.success is True
assert result.data["status"] == "failed"
assert result.data["error_message"] == "Network timeout"
class TestUpdateServiceReleaseNotes:
"""Test release notes retrieval."""
@pytest.mark.asyncio
async def test_get_release_notes_latest(self, mock_update_manager):
"""Test getting latest release notes."""
# Arrange
mock_update_manager.get_release_notes.return_value = "## Version 1.1.0\n- New feature"
service = UpdateService(mock_update_manager)
# Act
result = await service.get_release_notes(version=None)
# Assert
assert result.success is True
assert result.data["version"] == "latest"
assert "New feature" in result.data["release_notes"]
@pytest.mark.asyncio
async def test_get_release_notes_specific_version(self, mock_update_manager):
"""Test getting notes for specific version."""
# Arrange
mock_update_manager.get_release_notes.return_value = "## Version 1.0.0\n- Initial release"
service = UpdateService(mock_update_manager)
# Act
result = await service.get_release_notes(version="1.0.0")
# Assert
assert result.success is True
assert result.data["version"] == "1.0.0"
assert "Initial release" in result.data["release_notes"]
mock_update_manager.get_release_notes.assert_called_once_with("1.0.0")
class TestUpdateServiceCombinedOperations:
"""Test combined update operations."""
@pytest.mark.asyncio
async def test_check_and_download_update_available(self, mock_update_manager):
"""Test combined check and download when update is available."""
# Arrange
mock_update_manager.check_for_updates.return_value = {
"update_available": True,
"current_version": "1.0.0",
"latest_version": "1.1.0"
}
mock_update_manager.download_update.return_value = True
service = UpdateService(mock_update_manager)
# Act
result = await service.check_and_download_update()
# Assert
assert result.success is True
assert result.data["ready_to_install"] is True
assert "update_info" in result.data
mock_update_manager.download_update.assert_called_once()
@pytest.mark.asyncio
async def test_check_and_download_update_not_available(self, mock_update_manager):
"""Test combined check and download when no update available."""
# Arrange
mock_update_manager.check_for_updates.return_value = {
"update_available": False,
"current_version": "1.1.0"
}
service = UpdateService(mock_update_manager)
# Act
result = await service.check_and_download_update()
# Assert
assert result.success is True
assert result.data["update_available"] is False
mock_update_manager.download_update.assert_not_called()
@pytest.mark.asyncio
async def test_full_update_success(self, mock_update_manager):
"""Test full update workflow."""
# Arrange
mock_update_manager.check_for_updates.return_value = {
"update_available": True,
"current_version": "1.0.0",
"latest_version": "1.1.0"
}
mock_update_manager.download_update.return_value = True
mock_update_manager.install_update.return_value = True
service = UpdateService(mock_update_manager)
# Act
result = await service.full_update()
# Assert
assert result.success is True
assert result.data["update_performed"] is True
assert result.data["previous_version"] == "1.0.0"
assert result.data["new_version"] == "1.1.0"
assert result.data["requires_restart"] is True
# Verify all steps were called
mock_update_manager.check_for_updates.assert_called_once()
mock_update_manager.download_update.assert_called_once()
mock_update_manager.install_update.assert_called_once()
@pytest.mark.asyncio
async def test_full_update_already_up_to_date(self, mock_update_manager):
"""Test full update when already up to date."""
# Arrange
mock_update_manager.check_for_updates.return_value = {
"update_available": False,
"current_version": "1.1.0"
}
service = UpdateService(mock_update_manager)
# Act
result = await service.full_update()
# Assert
assert result.success is True
assert result.data["update_performed"] is False
mock_update_manager.download_update.assert_not_called()
mock_update_manager.install_update.assert_not_called()

View File

@@ -0,0 +1,391 @@
"""Unit tests for WiFiService.
Tests the WiFiService business logic layer, ensuring:
- WiFi status queries
- Network scanning and connection
- Mode switching
- Saved network management
- Error handling and error codes
"""
import pytest
from unittest.mock import AsyncMock, Mock
from app.backend.services.wifi_service import WiFiService
from app.backend.managers.wifi_manager import WiFiMode, WiFiStatus, WiFiNetwork, WiFiInterface
class TestWiFiServiceStatus:
"""Test WiFi status queries."""
@pytest.mark.asyncio
async def test_get_status_success(self, mock_wifi_manager):
"""Test successful WiFi status retrieval."""
# Arrange
test_interface = WiFiInterface(
name="wlan0",
mac="00:11:22:33:44:55",
is_usb=False,
is_up=True,
connected=False,
ssid=None,
ip_address="10.3.141.1"
)
mock_wifi_manager.get_status.return_value = WiFiStatus(
mode=WiFiMode.AP,
interfaces=[test_interface],
current_ssid=None,
current_ip=None,
ap_ssid="dangerous-pi",
ap_ip="10.3.141.1",
supports_dual=False
)
service = WiFiService(mock_wifi_manager)
# Act
result = await service.get_status()
# Assert
assert result.success is True
assert result.data["mode"] == "ap"
assert len(result.data["interfaces"]) == 1
assert result.data["interfaces"][0]["name"] == "wlan0"
assert result.data["access_point"]["ssid"] == "dangerous-pi"
assert result.data["supports_dual"] is False
@pytest.mark.asyncio
async def test_get_status_with_client_connection(self, mock_wifi_manager):
"""Test WiFi status with client connection."""
# Arrange
test_interface = WiFiInterface(
name="wlan0",
mac="00:11:22:33:44:55",
is_usb=False,
is_up=True,
connected=True,
ssid="MyNetwork",
ip_address="192.168.1.100"
)
mock_wifi_manager.get_status.return_value = WiFiStatus(
mode=WiFiMode.CLIENT,
interfaces=[test_interface],
current_ssid="MyNetwork",
current_ip="192.168.1.100",
ap_ssid="dangerous-pi",
ap_ip="10.3.141.1",
supports_dual=False
)
service = WiFiService(mock_wifi_manager)
# Act
result = await service.get_status()
# Assert
assert result.success is True
assert result.data["mode"] == "client"
assert result.data["client"]["ssid"] == "MyNetwork"
assert result.data["client"]["ip"] == "192.168.1.100"
@pytest.mark.asyncio
async def test_get_status_exception(self, mock_wifi_manager):
"""Test WiFi status query with exception."""
# Arrange
mock_wifi_manager.get_status.side_effect = Exception("WiFi error")
service = WiFiService(mock_wifi_manager)
# Act
result = await service.get_status()
# Assert
assert result.success is False
assert result.error.code == "wifi_status_error"
assert "WiFi error" in result.error.details
class TestWiFiServiceNetworkScanning:
"""Test network scanning."""
@pytest.mark.asyncio
async def test_scan_networks_success(self, mock_wifi_manager):
"""Test successful network scan."""
# Arrange
test_networks = [
WiFiNetwork(
ssid="TestNetwork",
bssid="AA:BB:CC:DD:EE:FF",
signal_strength=-45,
frequency=2437,
encrypted=True,
in_use=False
),
WiFiNetwork(
ssid="OpenNetwork",
bssid="11:22:33:44:55:66",
signal_strength=-65,
frequency=2462,
encrypted=False,
in_use=False
)
]
mock_wifi_manager.scan_networks.return_value = test_networks
service = WiFiService(mock_wifi_manager)
# Act
result = await service.scan_networks()
# Assert
assert result.success is True
assert result.data["count"] == 2
assert len(result.data["networks"]) == 2
assert result.data["networks"][0]["ssid"] == "TestNetwork"
assert result.data["networks"][0]["encrypted"] is True
assert result.data["networks"][1]["ssid"] == "OpenNetwork"
assert result.data["networks"][1]["encrypted"] is False
@pytest.mark.asyncio
async def test_scan_networks_with_interface(self, mock_wifi_manager):
"""Test network scan with specific interface."""
# Arrange
mock_wifi_manager.scan_networks.return_value = []
service = WiFiService(mock_wifi_manager)
# Act
result = await service.scan_networks(interface="wlan1")
# Assert
assert result.success is True
mock_wifi_manager.scan_networks.assert_called_once_with("wlan1")
@pytest.mark.asyncio
async def test_scan_networks_exception(self, mock_wifi_manager):
"""Test network scan with exception."""
# Arrange
mock_wifi_manager.scan_networks.side_effect = Exception("Scan failed")
service = WiFiService(mock_wifi_manager)
# Act
result = await service.scan_networks()
# Assert
assert result.success is False
assert result.error.code == "wifi_scan_error"
class TestWiFiServiceConnection:
"""Test network connection management."""
@pytest.mark.asyncio
async def test_connect_success(self, mock_wifi_manager):
"""Test successful network connection."""
# Arrange
mock_wifi_manager.connect_to_network.return_value = True
mock_wifi_manager.get_status.return_value = WiFiStatus(
mode=WiFiMode.CLIENT,
interfaces=[],
current_ssid="TestNetwork",
current_ip="192.168.1.100",
ap_ssid=None,
ap_ip=None,
supports_dual=False
)
service = WiFiService(mock_wifi_manager)
# Act
result = await service.connect(
ssid="TestNetwork",
password="testpass123"
)
# Assert
assert result.success is True
assert "TestNetwork" in result.data["message"]
assert result.data["ssid"] == "TestNetwork"
assert result.data["ip"] == "192.168.1.100"
mock_wifi_manager.connect_to_network.assert_called_once_with(
ssid="TestNetwork",
password="testpass123",
interface=None,
hidden=False
)
@pytest.mark.asyncio
async def test_connect_open_network(self, mock_wifi_manager):
"""Test connection to open network."""
# Arrange
mock_wifi_manager.connect_to_network.return_value = True
mock_wifi_manager.get_status.return_value = WiFiStatus(
mode=WiFiMode.CLIENT,
interfaces=[],
current_ssid="OpenNet",
current_ip="192.168.1.50",
ap_ssid=None,
ap_ip=None,
supports_dual=False
)
service = WiFiService(mock_wifi_manager)
# Act
result = await service.connect(ssid="OpenNet", password=None)
# Assert
assert result.success is True
mock_wifi_manager.connect_to_network.assert_called_once_with(
ssid="OpenNet",
password=None,
interface=None,
hidden=False
)
@pytest.mark.asyncio
async def test_connect_failure(self, mock_wifi_manager):
"""Test failed network connection."""
# Arrange
mock_wifi_manager.connect_to_network.return_value = False
service = WiFiService(mock_wifi_manager)
# Act
result = await service.connect(ssid="BadNetwork", password="wrong")
# Assert
assert result.success is False
assert result.error.code == "connection_failed"
@pytest.mark.asyncio
async def test_disconnect_success(self, mock_wifi_manager):
"""Test successful disconnect."""
# Arrange
mock_wifi_manager.disconnect_from_network.return_value = True
service = WiFiService(mock_wifi_manager)
# Act
result = await service.disconnect()
# Assert
assert result.success is True
assert "Disconnected" in result.data["message"]
@pytest.mark.asyncio
async def test_disconnect_failure(self, mock_wifi_manager):
"""Test failed disconnect."""
# Arrange
mock_wifi_manager.disconnect_from_network.return_value = False
service = WiFiService(mock_wifi_manager)
# Act
result = await service.disconnect()
# Assert
assert result.success is False
assert result.error.code == "disconnect_failed"
class TestWiFiServiceModeSwitch:
"""Test WiFi mode switching."""
@pytest.mark.asyncio
async def test_set_mode_ap(self, mock_wifi_manager):
"""Test switching to AP mode."""
# Arrange
mock_wifi_manager.set_mode.return_value = True
service = WiFiService(mock_wifi_manager)
# Act
result = await service.set_mode("ap")
# Assert
assert result.success is True
assert result.data["mode"] == "ap"
@pytest.mark.asyncio
async def test_set_mode_client(self, mock_wifi_manager):
"""Test switching to client mode."""
# Arrange
mock_wifi_manager.set_mode.return_value = True
service = WiFiService(mock_wifi_manager)
# Act
result = await service.set_mode("client")
# Assert
assert result.success is True
assert result.data["mode"] == "client"
@pytest.mark.asyncio
async def test_set_mode_invalid(self, mock_wifi_manager):
"""Test invalid mode."""
# Arrange
service = WiFiService(mock_wifi_manager)
# Act
result = await service.set_mode("invalid_mode")
# Assert
assert result.success is False
assert result.error.code == "invalid_mode"
@pytest.mark.asyncio
async def test_set_mode_dual_not_supported(self, mock_wifi_manager):
"""Test dual mode without USB adapter."""
# Arrange
mock_wifi_manager.set_mode.side_effect = ValueError("Dual mode requires USB WiFi adapter")
service = WiFiService(mock_wifi_manager)
# Act
result = await service.set_mode("dual")
# Assert
assert result.success is False
assert result.error.code == "mode_not_supported"
class TestWiFiServiceSavedNetworks:
"""Test saved network management."""
@pytest.mark.asyncio
async def test_get_saved_networks_success(self, mock_wifi_manager):
"""Test retrieving saved networks."""
# Arrange
saved = [
{"id": "0", "ssid": "HomeNet", "enabled": True},
{"id": "1", "ssid": "WorkNet", "enabled": False}
]
mock_wifi_manager.get_saved_networks.return_value = saved
service = WiFiService(mock_wifi_manager)
# Act
result = await service.get_saved_networks()
# Assert
assert result.success is True
assert result.data["count"] == 2
assert result.data["networks"][0]["ssid"] == "HomeNet"
@pytest.mark.asyncio
async def test_forget_network_success(self, mock_wifi_manager):
"""Test forgetting a saved network."""
# Arrange
mock_wifi_manager.forget_network.return_value = True
service = WiFiService(mock_wifi_manager)
# Act
result = await service.forget_network("OldNetwork")
# Assert
assert result.success is True
assert "OldNetwork" in result.data["message"]
mock_wifi_manager.forget_network.assert_called_once_with("OldNetwork")
@pytest.mark.asyncio
async def test_forget_network_not_found(self, mock_wifi_manager):
"""Test forgetting non-existent network."""
# Arrange
mock_wifi_manager.forget_network.return_value = False
service = WiFiService(mock_wifi_manager)
# Act
result = await service.forget_network("NonExistent")
# Assert
assert result.success is False
assert result.error.code == "network_not_found"