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:
0
tests/unit/services/__init__.py
Normal file
0
tests/unit/services/__init__.py
Normal file
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"
|
||||
300
tests/unit/services/test_system_service.py
Normal file
300
tests/unit/services/test_system_service.py
Normal 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
|
||||
369
tests/unit/services/test_update_service.py
Normal file
369
tests/unit/services/test_update_service.py
Normal 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()
|
||||
391
tests/unit/services/test_wifi_service.py
Normal file
391
tests/unit/services/test_wifi_service.py
Normal 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"
|
||||
Reference in New Issue
Block a user