Files
pi-pm3/tests/unit/services/test_update_service.py
michael 4f35df1781 Initial commit - Phase 3/4
🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-06 13:46:22 -08:00

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()