webdrop-bridge/tests/integration/test_update_flow.py

203 lines
6.8 KiB
Python

"""Integration tests for the complete update flow."""
import asyncio
import json
from pathlib import Path
from unittest.mock import AsyncMock, MagicMock, patch
import pytest
from webdrop_bridge.config import Config
from webdrop_bridge.core.updater import Release, UpdateManager
@pytest.fixture
def config(tmp_path):
"""Create test config."""
return Config(
app_name="Test WebDrop",
app_version="0.0.1",
log_level="INFO",
log_file=None,
allowed_roots=[tmp_path],
allowed_urls=[],
webapp_url="file:///./webapp/index.html",
window_width=800,
window_height=600,
enable_logging=False,
)
@pytest.fixture
def mock_forgejo_response():
"""Mock Forgejo API response - formatted as returned by _fetch_release."""
return {
"tag_name": "v0.0.2",
"name": "WebDropBridge v0.0.2",
"version": "0.0.2", # _fetch_release adds this
"body": "## Bug Fixes\n- Fixed drag and drop on macOS",
"assets": [
{
"name": "WebDropBridge.exe",
"browser_download_url": "https://git.him-tools.de/HIM-public/webdrop-bridge/releases/download/v0.0.2/WebDropBridge.exe",
},
{
"name": "WebDropBridge.exe.sha256",
"browser_download_url": "https://git.him-tools.de/HIM-public/webdrop-bridge/releases/download/v0.0.2/WebDropBridge.exe.sha256",
},
],
"published_at": "2026-01-29T10:00:00Z",
}
class TestUpdateFlowIntegration:
"""Integration tests for the complete update check flow."""
@pytest.mark.asyncio
async def test_full_update_check_flow(self, config, mock_forgejo_response, tmp_path):
"""Test complete flow: API query -> version check -> signal."""
manager = UpdateManager(
current_version=config.app_version,
config_dir=tmp_path
)
# Mock the API fetch
with patch.object(manager, "_fetch_release") as mock_fetch:
mock_fetch.return_value = mock_forgejo_response
# Run check
release = await manager.check_for_updates()
# Verify API was called
mock_fetch.assert_called_once()
# Verify we got a release
assert release is not None
assert release.version == "0.0.2"
assert release.tag_name == "v0.0.2"
assert len(release.assets) == 2
@pytest.mark.asyncio
async def test_update_check_with_cache(self, config, mock_forgejo_response, tmp_path):
"""Test that cache is used on second call."""
manager = UpdateManager(
current_version=config.app_version,
config_dir=tmp_path
)
with patch.object(manager, "_fetch_release") as mock_fetch:
mock_fetch.return_value = mock_forgejo_response
# First call - should fetch from API
release1 = await manager.check_for_updates()
assert mock_fetch.call_count == 1
# Second call - should use cache
release2 = await manager.check_for_updates()
assert mock_fetch.call_count == 1 # Still 1, cache used
# Verify both got same result
assert release1.version == release2.version
@pytest.mark.asyncio
async def test_update_check_no_newer_version(self, config, tmp_path):
"""Test that no update available when latest is same version."""
manager = UpdateManager(
current_version="0.0.2",
config_dir=tmp_path
)
response = {
"tag_name": "v0.0.2",
"name": "WebDropBridge v0.0.2",
"body": "",
"assets": [],
"published_at": "2026-01-29T10:00:00Z",
}
with patch.object(manager, "_fetch_release") as mock_fetch:
mock_fetch.return_value = response
release = await manager.check_for_updates()
# Should return None since version is not newer
assert release is None
@pytest.mark.asyncio
async def test_update_check_network_error(self, config, tmp_path):
"""Test graceful handling of network errors."""
manager = UpdateManager(
current_version=config.app_version,
config_dir=tmp_path
)
# Mock network error
with patch.object(manager, "_fetch_release") as mock_fetch:
mock_fetch.side_effect = Exception("Connection timeout")
release = await manager.check_for_updates()
# Should return None on error
assert release is None
@pytest.mark.asyncio
async def test_version_parsing_in_api_response(self, config, tmp_path):
"""Test that version is correctly extracted from tag_name."""
manager = UpdateManager(
current_version=config.app_version,
config_dir=tmp_path
)
# API returns version with 'v' prefix - but _fetch_release processes it
response = {
"tag_name": "v1.2.3",
"name": "Release",
"version": "1.2.3", # _fetch_release adds this
"body": "",
"assets": [],
"published_at": "2026-01-29T10:00:00Z",
}
with patch.object(manager, "_fetch_release") as mock_fetch:
mock_fetch.return_value = response
release = await manager.check_for_updates()
# Version should be extracted correctly (without 'v')
assert release.version == "1.2.3"
@pytest.mark.asyncio
async def test_asset_parsing_in_release(self, config, mock_forgejo_response, tmp_path):
"""Test that release assets are correctly parsed."""
manager = UpdateManager(
current_version=config.app_version,
config_dir=tmp_path
)
with patch.object(manager, "_fetch_release") as mock_fetch:
mock_fetch.return_value = mock_forgejo_response
release = await manager.check_for_updates()
# Should have both exe and checksum
assert len(release.assets) == 2
asset_names = [a["name"] for a in release.assets]
assert "WebDropBridge.exe" in asset_names
assert "WebDropBridge.exe.sha256" in asset_names
@pytest.mark.asyncio
async def test_changelog_preserved(self, config, mock_forgejo_response, tmp_path):
"""Test that release notes/changelog are preserved."""
manager = UpdateManager(
current_version=config.app_version,
config_dir=tmp_path
)
with patch.object(manager, "_fetch_release") as mock_fetch:
mock_fetch.return_value = mock_forgejo_response
release = await manager.check_for_updates()
# Changelog should be available
assert release.body == mock_forgejo_response["body"]
assert "Bug Fixes" in release.body