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