feat: add startup forced update check
This commit is contained in:
+92
-1
@@ -12,7 +12,7 @@ sys.path.insert(0, os.path.dirname(__file__))
|
||||
from _helpers import TempDirMixin
|
||||
|
||||
from app import gui
|
||||
from app import accounts, ai, appconfig, db, prompts
|
||||
from app import accounts, ai, appconfig, db, prompts, update_check
|
||||
|
||||
if gui.QT_IMPORT_ERROR is not None:
|
||||
raise unittest.SkipTest("PySide6 未安装")
|
||||
@@ -203,6 +203,97 @@ class GuiTests(TempDirMixin, unittest.TestCase):
|
||||
|
||||
self.assert_removed(temp_dir)
|
||||
|
||||
def test_startup_update_gate_forced_blocks_and_opens_download(self):
|
||||
boxes = []
|
||||
|
||||
class FakeButton:
|
||||
def __init__(self, label):
|
||||
self.label = label
|
||||
self.enabled = True
|
||||
|
||||
def setEnabled(self, enabled):
|
||||
self.enabled = enabled
|
||||
|
||||
class FakeMessageBox:
|
||||
Warning = object()
|
||||
AcceptRole = object()
|
||||
RejectRole = object()
|
||||
|
||||
def __init__(self, parent=None):
|
||||
self.parent = parent
|
||||
self.icon = None
|
||||
self.title = ""
|
||||
self.text = ""
|
||||
self.informative_text = ""
|
||||
self.buttons = {}
|
||||
self.default_button = None
|
||||
boxes.append(self)
|
||||
|
||||
def setIcon(self, icon):
|
||||
self.icon = icon
|
||||
|
||||
def setWindowTitle(self, title):
|
||||
self.title = title
|
||||
|
||||
def setText(self, text):
|
||||
self.text = text
|
||||
|
||||
def setInformativeText(self, text):
|
||||
self.informative_text = text
|
||||
|
||||
def addButton(self, label, role):
|
||||
button = FakeButton(label)
|
||||
self.buttons[label] = button
|
||||
return button
|
||||
|
||||
def setDefaultButton(self, button):
|
||||
self.default_button = button
|
||||
|
||||
def exec(self):
|
||||
return 0
|
||||
|
||||
def clickedButton(self):
|
||||
return self.buttons["下载新版"]
|
||||
|
||||
result = update_check.UpdateCheckResult(
|
||||
current_version="1.0.0",
|
||||
checked=True,
|
||||
forced=True,
|
||||
latest_version="1.2.0",
|
||||
min_supported_version="1.1.0",
|
||||
download_url="https://example.test/cmshopee.zip",
|
||||
message="必须升级",
|
||||
)
|
||||
opened = []
|
||||
|
||||
with mock.patch("app.gui.QMessageBox", FakeMessageBox):
|
||||
allowed = gui._run_startup_update_gate(
|
||||
checker=lambda: result,
|
||||
opener=opened.append,
|
||||
)
|
||||
|
||||
self.assertFalse(allowed)
|
||||
self.assertEqual(["https://example.test/cmshopee.zip"], opened)
|
||||
self.assertEqual("必须升级", boxes[0].title)
|
||||
self.assertIn("当前版本:1.0.0", boxes[0].informative_text)
|
||||
self.assertIn("线上版本:1.2.0", boxes[0].informative_text)
|
||||
self.assertIn("保留 data/ 目录", boxes[0].informative_text)
|
||||
self.assertEqual(boxes[0].buttons["下载新版"], boxes[0].default_button)
|
||||
|
||||
def test_startup_update_gate_check_failure_allows_entry_and_logs(self):
|
||||
result = update_check.UpdateCheckResult(
|
||||
current_version="1.0.0",
|
||||
checked=True,
|
||||
forced=False,
|
||||
error="启动版本检查失败,已允许继续使用:网络超时",
|
||||
)
|
||||
|
||||
with mock.patch("app.gui.diagnostics.write_diagnostic_log") as write_log:
|
||||
allowed = gui._run_startup_update_gate(checker=lambda: result)
|
||||
|
||||
self.assertTrue(allowed)
|
||||
write_log.assert_called_once()
|
||||
|
||||
def test_status_callbacks_classify_success_warning_and_failure(self):
|
||||
with self.make_temp_dir() as temp_dir:
|
||||
statuses = []
|
||||
|
||||
@@ -0,0 +1,122 @@
|
||||
import socket
|
||||
import os
|
||||
import sys
|
||||
import unittest
|
||||
|
||||
sys.path.insert(0, os.path.dirname(__file__))
|
||||
from _helpers import REPO_ROOT
|
||||
|
||||
from app import update_check
|
||||
|
||||
assert REPO_ROOT
|
||||
|
||||
|
||||
class UpdateCheckTests(unittest.TestCase):
|
||||
def test_compare_versions_uses_numeric_segments(self):
|
||||
self.assertGreater(update_check.compare_versions("0.10.0", "0.2.0"), 0)
|
||||
self.assertEqual(0, update_check.compare_versions("1.2", "1.2.0"))
|
||||
self.assertLess(update_check.compare_versions("v1.2.3", "1.2.4"), 0)
|
||||
|
||||
def test_forced_update_by_min_supported_version(self):
|
||||
info = update_check.UpdateInfo(
|
||||
latest_version="1.2.0",
|
||||
min_supported_version="1.1.0",
|
||||
force_update=False,
|
||||
)
|
||||
|
||||
self.assertTrue(update_check.is_forced_update(info, "1.0.9"))
|
||||
self.assertFalse(update_check.is_forced_update(info, "1.1.0"))
|
||||
|
||||
def test_forced_update_by_force_flag_and_latest_version(self):
|
||||
forced = update_check.UpdateInfo(latest_version="1.2.0", force_update=True)
|
||||
optional = update_check.UpdateInfo(latest_version="1.2.0", force_update=False)
|
||||
|
||||
self.assertTrue(update_check.is_forced_update(forced, "1.1.9"))
|
||||
self.assertFalse(update_check.is_forced_update(optional, "1.1.9"))
|
||||
self.assertFalse(update_check.is_forced_update(forced, "1.2.0"))
|
||||
|
||||
def test_check_for_update_forced_response(self):
|
||||
def fetcher(_url, _timeout):
|
||||
return {
|
||||
"latest_version": "1.2.0",
|
||||
"min_supported_version": "1.1.0",
|
||||
"force_update": True,
|
||||
"download_url": "https://example.test/cmshopee.zip",
|
||||
"sha256": "abc",
|
||||
"message": "请升级后继续使用",
|
||||
}
|
||||
|
||||
result = update_check.check_for_update(
|
||||
current_version="1.0.0",
|
||||
url="https://example.test/version.json",
|
||||
fetcher=fetcher,
|
||||
)
|
||||
|
||||
self.assertTrue(result.checked)
|
||||
self.assertTrue(result.forced)
|
||||
self.assertFalse(result.can_enter)
|
||||
self.assertEqual("1.2.0", result.latest_version)
|
||||
self.assertEqual("https://example.test/cmshopee.zip", result.download_url)
|
||||
|
||||
def test_check_for_update_accepts_release_wrapper_response(self):
|
||||
def fetcher(_url, _timeout):
|
||||
return {
|
||||
"platform": "windows",
|
||||
"release": {
|
||||
"version": "0.1.1",
|
||||
"download_url": "https://example.test/cmshopee-0.1.1.zip",
|
||||
"sha256": "abc",
|
||||
"release_notes": "新版说明",
|
||||
},
|
||||
}
|
||||
|
||||
result = update_check.check_for_update(
|
||||
current_version="0.1.0",
|
||||
url="https://example.test/releases/latest?platform=windows",
|
||||
fetcher=fetcher,
|
||||
)
|
||||
|
||||
self.assertTrue(result.checked)
|
||||
self.assertFalse(result.forced)
|
||||
self.assertEqual("0.1.1", result.latest_version)
|
||||
self.assertEqual("https://example.test/cmshopee-0.1.1.zip", result.download_url)
|
||||
self.assertEqual("abc", result.sha256)
|
||||
self.assertEqual("新版说明", result.message)
|
||||
|
||||
def test_network_failure_allows_entry(self):
|
||||
def fetcher(_url, _timeout):
|
||||
raise socket.timeout("timeout")
|
||||
|
||||
result = update_check.check_for_update(
|
||||
current_version="1.0.0",
|
||||
url="https://example.test/version.json",
|
||||
fetcher=fetcher,
|
||||
)
|
||||
|
||||
self.assertTrue(result.checked)
|
||||
self.assertFalse(result.forced)
|
||||
self.assertTrue(result.can_enter)
|
||||
self.assertIn("启动版本检查失败", result.error)
|
||||
|
||||
def test_invalid_response_allows_entry(self):
|
||||
result = update_check.check_for_update(
|
||||
current_version="1.0.0",
|
||||
url="https://example.test/version.json",
|
||||
fetcher=lambda _url, _timeout: {"message": "missing versions"},
|
||||
)
|
||||
|
||||
self.assertTrue(result.checked)
|
||||
self.assertFalse(result.forced)
|
||||
self.assertTrue(result.can_enter)
|
||||
self.assertIn("缺少 latest_version", result.error)
|
||||
|
||||
def test_empty_update_url_skips_check(self):
|
||||
result = update_check.check_for_update(current_version="1.0.0", url="")
|
||||
|
||||
self.assertFalse(result.checked)
|
||||
self.assertFalse(result.forced)
|
||||
self.assertEqual("", result.error)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Reference in New Issue
Block a user