🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
301 lines
10 KiB
Python
301 lines
10 KiB
Python
"""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
|