buzz/tests/update_checker_test.py
2026-02-28 14:39:04 +00:00

202 lines
8.6 KiB
Python

import platform
from datetime import datetime, timedelta
from unittest.mock import patch
import pytest
from pytestqt.qtbot import QtBot
from buzz.__version__ import VERSION
from buzz.settings.settings import Settings
from buzz.update_checker import UpdateChecker, UpdateInfo
from tests.mock_qt import MockNetworkAccessManager, MockNetworkReply
VERSION_INFO = {
"version": "99.0.0",
"release_notes": "Some fixes.",
"download_urls": {
"windows_x64": ["https://example.com/Buzz-99.0.0.exe"],
"macos_arm": ["https://example.com/Buzz-99.0.0-arm.dmg"],
"macos_x86": ["https://example.com/Buzz-99.0.0-x86.dmg"],
},
}
@pytest.fixture()
def checker(settings: Settings) -> UpdateChecker:
reply = MockNetworkReply(data=VERSION_INFO)
manager = MockNetworkAccessManager(reply=reply)
return UpdateChecker(settings=settings, network_manager=manager)
class TestShouldCheckForUpdates:
def test_returns_false_on_linux(self, checker: UpdateChecker):
with patch.object(platform, "system", return_value="Linux"):
assert checker.should_check_for_updates() is False
def test_returns_true_on_windows_first_run(self, checker: UpdateChecker, settings: Settings):
settings.set_value(Settings.Key.LAST_UPDATE_CHECK, "")
with patch.object(platform, "system", return_value="Windows"):
assert checker.should_check_for_updates() is True
def test_returns_true_on_macos_first_run(self, checker: UpdateChecker, settings: Settings):
settings.set_value(Settings.Key.LAST_UPDATE_CHECK, "")
with patch.object(platform, "system", return_value="Darwin"):
assert checker.should_check_for_updates() is True
def test_returns_false_when_checked_recently(
self, checker: UpdateChecker, settings: Settings
):
recent = (datetime.now() - timedelta(days=2)).isoformat()
settings.set_value(Settings.Key.LAST_UPDATE_CHECK, recent)
with patch.object(platform, "system", return_value="Windows"):
assert checker.should_check_for_updates() is False
def test_returns_true_when_check_is_overdue(
self, checker: UpdateChecker, settings: Settings
):
old = (datetime.now() - timedelta(days=10)).isoformat()
settings.set_value(Settings.Key.LAST_UPDATE_CHECK, old)
with patch.object(platform, "system", return_value="Windows"):
assert checker.should_check_for_updates() is True
def test_returns_true_on_invalid_date_in_settings(
self, checker: UpdateChecker, settings: Settings
):
settings.set_value(Settings.Key.LAST_UPDATE_CHECK, "not-a-date")
with patch.object(platform, "system", return_value="Windows"):
assert checker.should_check_for_updates() is True
class TestIsNewerVersion:
def test_newer_major(self, checker: UpdateChecker):
with patch("buzz.update_checker.VERSION", "1.0.0"):
assert checker._is_newer_version("2.0.0") is True
def test_newer_minor(self, checker: UpdateChecker):
with patch("buzz.update_checker.VERSION", "1.0.0"):
assert checker._is_newer_version("1.1.0") is True
def test_newer_patch(self, checker: UpdateChecker):
with patch("buzz.update_checker.VERSION", "1.0.0"):
assert checker._is_newer_version("1.0.1") is True
def test_same_version(self, checker: UpdateChecker):
with patch("buzz.update_checker.VERSION", "1.0.0"):
assert checker._is_newer_version("1.0.0") is False
def test_older_version(self, checker: UpdateChecker):
with patch("buzz.update_checker.VERSION", "2.0.0"):
assert checker._is_newer_version("1.9.9") is False
def test_different_segment_count(self, checker: UpdateChecker):
with patch("buzz.update_checker.VERSION", "1.0"):
assert checker._is_newer_version("1.0.1") is True
def test_invalid_version_returns_false(self, checker: UpdateChecker):
with patch("buzz.update_checker.VERSION", "1.0.0"):
assert checker._is_newer_version("not-a-version") is False
class TestGetDownloadUrl:
def test_windows_returns_windows_urls(self, checker: UpdateChecker):
with patch.object(platform, "system", return_value="Windows"):
urls = checker._get_download_url(VERSION_INFO["download_urls"])
assert urls == ["https://example.com/Buzz-99.0.0.exe"]
def test_macos_arm_returns_arm_urls(self, checker: UpdateChecker):
with patch.object(platform, "system", return_value="Darwin"), \
patch.object(platform, "machine", return_value="arm64"):
urls = checker._get_download_url(VERSION_INFO["download_urls"])
assert urls == ["https://example.com/Buzz-99.0.0-arm.dmg"]
def test_macos_x86_returns_x86_urls(self, checker: UpdateChecker):
with patch.object(platform, "system", return_value="Darwin"), \
patch.object(platform, "machine", return_value="x86_64"):
urls = checker._get_download_url(VERSION_INFO["download_urls"])
assert urls == ["https://example.com/Buzz-99.0.0-x86.dmg"]
def test_linux_returns_empty(self, checker: UpdateChecker):
with patch.object(platform, "system", return_value="Linux"):
urls = checker._get_download_url(VERSION_INFO["download_urls"])
assert urls == []
def test_wraps_plain_string_in_list(self, checker: UpdateChecker):
with patch.object(platform, "system", return_value="Windows"):
urls = checker._get_download_url({"windows_x64": "https://example.com/a.exe"})
assert urls == ["https://example.com/a.exe"]
class TestCheckForUpdates:
def _make_checker(self, settings: Settings, version_data: dict) -> UpdateChecker:
settings.set_value(Settings.Key.LAST_UPDATE_CHECK, "")
reply = MockNetworkReply(data=version_data)
manager = MockNetworkAccessManager(reply=reply)
return UpdateChecker(settings=settings, network_manager=manager)
def test_emits_update_available_when_newer_version(self, settings: Settings):
received = []
checker = self._make_checker(settings, VERSION_INFO)
checker.update_available.connect(lambda info: received.append(info))
with patch.object(platform, "system", return_value="Windows"), \
patch.object(platform, "machine", return_value="x86_64"), \
patch("buzz.update_checker.VERSION", "1.0.0"):
checker.check_for_updates()
assert len(received) == 1
update_info: UpdateInfo = received[0]
assert update_info.version == "99.0.0"
assert update_info.release_notes == "Some fixes."
assert update_info.download_urls == ["https://example.com/Buzz-99.0.0.exe"]
def test_does_not_emit_when_version_is_current(self, settings: Settings):
received = []
checker = self._make_checker(settings, {**VERSION_INFO, "version": VERSION})
checker.update_available.connect(lambda info: received.append(info))
with patch.object(platform, "system", return_value="Windows"):
checker.check_for_updates()
assert received == []
def test_skips_network_call_on_linux(self, settings: Settings):
received = []
checker = self._make_checker(settings, VERSION_INFO)
checker.update_available.connect(lambda info: received.append(info))
with patch.object(platform, "system", return_value="Linux"):
checker.check_for_updates()
assert received == []
def test_stores_last_check_date_after_reply(self, settings: Settings):
checker = self._make_checker(settings, {**VERSION_INFO, "version": VERSION})
with patch.object(platform, "system", return_value="Windows"):
checker.check_for_updates()
stored = settings.value(Settings.Key.LAST_UPDATE_CHECK, "")
assert stored != ""
datetime.fromisoformat(stored) # should not raise
def test_stores_available_version_when_update_found(self, settings: Settings):
checker = self._make_checker(settings, VERSION_INFO)
with patch.object(platform, "system", return_value="Windows"), \
patch("buzz.update_checker.VERSION", "1.0.0"):
checker.check_for_updates()
assert settings.value(Settings.Key.UPDATE_AVAILABLE_VERSION, "") == "99.0.0"
def test_clears_available_version_when_up_to_date(self, settings: Settings):
settings.set_value(Settings.Key.UPDATE_AVAILABLE_VERSION, "99.0.0")
checker = self._make_checker(settings, {**VERSION_INFO, "version": VERSION})
with patch.object(platform, "system", return_value="Windows"):
checker.check_for_updates()
assert settings.value(Settings.Key.UPDATE_AVAILABLE_VERSION, "") == ""