"""设置页在线更新 UI 和后台线程测试。""" import os import tempfile import time import unittest from pathlib import Path from unittest.mock import patch os.environ.setdefault("QT_QPA_PLATFORM", "offscreen") from PyQt5.QtCore import QTimer from PyQt5.QtTest import QTest from PyQt5.QtWidgets import QApplication from src.mock_admin_gateway import MockAdminGateway from src.settings_repository import SettingsRepository from src.settings_ui import SettingsPage from src.update_service import ( UPDATE_MANIFEST_SETTING, UpdateCheckResult, UpdateInfo, ) MANIFEST_URL = "https://updates.example.test/autobuy%E2%80%94%E2%80%94manifest.json" class FakeUpdateService: def __init__(self, result=None, delay=0.0, download_error=None): self.result = result or UpdateCheckResult("0.2.0", "0.2.0", False) self.delay = delay self.download_error = download_error self.check_count = 0 self.download_count = 0 def check(self, manifest_url, current_version="0.1.0", is_cancelled=None): self.check_count += 1 self.manifest_url = manifest_url if self.delay: time.sleep(self.delay) return self.result def download_and_stage(self, update, is_cancelled=None, on_progress=None): self.download_count += 1 if on_progress is not None: on_progress(50) if self.download_error is not None: raise self.download_error if on_progress is not None: on_progress(100) return Path("app.new") class FakeButton: def setText(self, _text): pass def setFocus(self): pass class AcceptDownloadMessageBox: def __init__(self, _title, _content, _parent): self.yesButton = FakeButton() self.cancelButton = FakeButton() def exec(self): return True class UpdateUiEventTest(unittest.TestCase): @classmethod def setUpClass(cls): cls.app = QApplication.instance() or QApplication([]) def setUp(self): self.temporary_directory = tempfile.TemporaryDirectory() self.repository = SettingsRepository( Path(self.temporary_directory.name) / "client.db" ) def tearDown(self): self.temporary_directory.cleanup() def _page(self, service): return SettingsPage( settings_repository=self.repository, admin_gateway=MockAdminGateway(), update_service=service, ) def _wait_until(self, predicate, timeout_ms=2000): elapsed = 0 while not predicate() and elapsed < timeout_ms: QTest.qWait(10) elapsed += 10 self.assertTrue(predicate(), "等待在线更新线程超时") def test_update_card_loads_saved_url_and_current_version(self): self.repository.set(UPDATE_MANIFEST_SETTING, MANIFEST_URL) page = self._page(FakeUpdateService()) self.assertEqual(page.currentVersionLabel.text(), "0.2.0") self.assertEqual(page.updateManifestUrlInput.text(), MANIFEST_URL) self.assertTrue(page.updateCheckButton.isEnabled()) page.eventBinder.shutdown() page.deleteLater() def test_http_url_is_rejected_before_starting_worker(self): service = FakeUpdateService() page = self._page(service) page.updateManifestUrlInput.setText("http://updates.example.test/manifest.json") page.updateCheckButton.click() self.assertIn("必须是有效的 HTTPS", page.updateStatusLabel.text()) self.assertEqual(service.check_count, 0) page.eventBinder.shutdown() page.deleteLater() def test_slow_check_does_not_block_or_start_twice(self): service = FakeUpdateService(delay=0.08) page = self._page(service) page.updateManifestUrlInput.setText(MANIFEST_URL) timer_fired = [] QTimer.singleShot(10, lambda: timer_fired.append(True)) page.updateCheckButton.click() page.eventBinder.updateEventBinder.request_check() self._wait_until(lambda: bool(timer_fired), timeout_ms=500) self._wait_until( lambda: page.eventBinder.updateEventBinder._check_thread is None ) self.assertEqual(service.check_count, 1) self.assertEqual( self.repository.get(UPDATE_MANIFEST_SETTING), MANIFEST_URL ) self.assertIn("当前已是最新版本", page.updateStatusLabel.text()) page.eventBinder.shutdown() page.deleteLater() def test_confirmed_new_version_downloads_and_stages(self): update = UpdateInfo( version="0.2.0", manifest_url=MANIFEST_URL, update_url="https://updates.example.test/CMAutoBuy-0.2.0-update.zip", file_name="CMAutoBuy-0.2.0-update.zip", size=1024, sha256="0" * 64, ) service = FakeUpdateService( UpdateCheckResult("0.1.0", "0.2.0", True, update) ) page = self._page(service) page.updateManifestUrlInput.setText(MANIFEST_URL) with patch("src.update_ui_event.MessageBox", AcceptDownloadMessageBox): page.updateCheckButton.click() self._wait_until( lambda: page.eventBinder.updateEventBinder._check_thread is None and page.eventBinder.updateEventBinder._download_thread is None ) self.assertEqual(service.download_count, 1) self.assertIn("已准备好", page.updateStatusLabel.text()) self.assertIn("重新启动", page.updateStatusLabel.text()) page.eventBinder.shutdown() page.deleteLater() if __name__ == "__main__": unittest.main()