134 lines
5.7 KiB
Python
134 lines
5.7 KiB
Python
import unittest
|
|
from unittest import mock
|
|
|
|
from PySide6.QtWidgets import QApplication, QDialog
|
|
|
|
import startup_update as update
|
|
|
|
|
|
class StartupUpdateTest(unittest.TestCase):
|
|
@classmethod
|
|
def setUpClass(cls):
|
|
cls.app = QApplication.instance() or QApplication([])
|
|
|
|
def setUp(self):
|
|
self.dialog = QDialog()
|
|
self.dialog._busy = mock.Mock()
|
|
self.dialog._set_status = mock.Mock()
|
|
self.check = update.LoginUpdateCheck(self.dialog)
|
|
self.resume = mock.Mock()
|
|
self.check.resume = self.resume
|
|
self.check.phase = "checking"
|
|
for name, value in (("load_settings", {"server_url": "https://backend.example"}), ("cached_release_status", {})):
|
|
patcher = mock.patch.object(update.backend_client, name, return_value=value)
|
|
patcher.start()
|
|
self.addCleanup(patcher.stop)
|
|
patcher = mock.patch.object(update.backend_client, "save_settings")
|
|
patcher.start()
|
|
self.addCleanup(patcher.stop)
|
|
self.addCleanup(self.dialog.close)
|
|
|
|
def release(self, **values):
|
|
return {"latest_version": "99.0.0", "update_available": True,
|
|
"eligible": True, "ready_for_auto_update": True, **values}
|
|
|
|
def test_no_update_continues_without_prompt(self):
|
|
with mock.patch.object(self.check, "_offer") as offer:
|
|
self.check._checked({"release": {"update_available": False}})
|
|
self.resume.assert_called_once()
|
|
offer.assert_not_called()
|
|
|
|
def test_ineligible_release_does_not_prompt(self):
|
|
with mock.patch.object(self.check, "_offer") as offer:
|
|
self.check._checked({"release": self.release(eligible=False)})
|
|
self.resume.assert_called_once()
|
|
offer.assert_not_called()
|
|
|
|
def test_timeout_continues_once_and_ignores_late_results(self):
|
|
self.check._timeout()
|
|
self.check._checked({"release": self.release(force_upgrade=True)})
|
|
self.resume.assert_called_once()
|
|
|
|
def test_network_failure_continues_login(self):
|
|
self.check._checked({"error": "offline"})
|
|
self.resume.assert_called_once()
|
|
|
|
def test_known_forced_upgrade_is_preserved_offline(self):
|
|
with mock.patch.object(update.backend_client, "cached_release_status", return_value=self.release(force_upgrade=True)), \
|
|
mock.patch.object(self.check, "_offer") as offer:
|
|
self.check._checked({"error": "offline"})
|
|
offer.assert_called_once()
|
|
self.resume.assert_not_called()
|
|
|
|
def test_available_update_prompts_before_login(self):
|
|
with mock.patch.object(self.check, "_offer") as offer:
|
|
self.check._checked({"release": self.release()})
|
|
offer.assert_called_once()
|
|
self.resume.assert_not_called()
|
|
|
|
def test_auto_install_policy_starts_downloading(self):
|
|
with mock.patch.object(self.check, "_download") as download:
|
|
self.check._checked({"release": self.release(auto_install=True)})
|
|
download.assert_called_once()
|
|
self.resume.assert_not_called()
|
|
|
|
def test_optional_update_can_be_deferred(self):
|
|
self.check.release = self.release()
|
|
with mock.patch.object(update, "QMessageBox") as box:
|
|
box.return_value.clickedButton.return_value = None
|
|
self.check._offer()
|
|
self.resume.assert_called_once()
|
|
|
|
def test_forced_update_cannot_be_deferred(self):
|
|
self.check.release = self.release(force_upgrade=True)
|
|
with mock.patch.object(update, "QMessageBox") as box:
|
|
box.return_value.clickedButton.return_value = None
|
|
self.check._offer()
|
|
self.assertEqual(self.check.phase, "closed")
|
|
self.resume.assert_not_called()
|
|
|
|
def test_downloaded_update_launches_helper_and_exits_login(self):
|
|
self.check.phase = "downloading"
|
|
self.check.release = self.release()
|
|
with mock.patch.object(update.desktop_updater, "launch_installer") as launch:
|
|
self.check._downloaded({"installer": "probe.exe"})
|
|
launch.assert_called_once()
|
|
self.assertEqual(self.check.phase, "closed")
|
|
self.resume.assert_not_called()
|
|
|
|
def test_optional_download_failure_allows_login(self):
|
|
self.check.phase = "downloading"
|
|
self.check.release = self.release()
|
|
with mock.patch.object(update.QMessageBox, "warning"):
|
|
self.check._downloaded({"error": "hash mismatch"})
|
|
self.resume.assert_called_once()
|
|
|
|
def test_forced_download_failure_does_not_allow_login(self):
|
|
self.check.phase = "downloading"
|
|
self.check.release = self.release(force_upgrade=True)
|
|
with mock.patch.object(update.QMessageBox, "warning"):
|
|
self.check._downloaded({"error": "hash mismatch"})
|
|
self.resume.assert_not_called()
|
|
self.assertEqual(self.check.phase, "closed")
|
|
|
|
def test_closed_window_ignores_download_completion(self):
|
|
self.check._closed()
|
|
with mock.patch.object(update.desktop_updater, "launch_installer") as launch:
|
|
self.check._downloaded({"installer": "probe.exe"})
|
|
launch.assert_not_called()
|
|
|
|
def test_check_starts_without_login_and_uses_public_manifest(self):
|
|
self.check.phase = "idle"
|
|
with mock.patch.object(update.threading, "Thread") as thread, \
|
|
mock.patch.object(update.backend_client, "device_id", return_value="probe-device"), \
|
|
mock.patch.object(update.desktop_updater, "fetch_manifest", return_value={"update_available": False}) as fetch:
|
|
self.check.start(self.resume)
|
|
self.resume.assert_not_called()
|
|
thread.call_args.kwargs["target"]()
|
|
fetch.assert_called_once_with("https://backend.example", "probe-device", timeout=6.0)
|
|
self.resume.assert_called_once()
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|