Files
kefu/wechat_rpa/test_desktop_updater.py
T
2026-09-21 10:34:06 +08:00

139 lines
4.8 KiB
Python

"""自动升级清单、下载与校验测试。"""
from __future__ import annotations
import hashlib
import io
import tempfile
import unittest
from pathlib import Path
from unittest import mock
import backend_client
import desktop_updater
class _DownloadResponse:
def __init__(self, content: bytes, *, status: int = 200):
self._stream = io.BytesIO(content)
self.status = status
self.headers = {"Content-Length": str(len(content))}
def read(self, size: int = -1) -> bytes:
return self._stream.read(size)
def __enter__(self):
return self
def __exit__(self, *_args):
return False
class DesktopUpdaterTest(unittest.TestCase):
def setUp(self) -> None:
self.temporary = tempfile.TemporaryDirectory()
self.root = Path(self.temporary.name)
self.patches = [
mock.patch.object(desktop_updater, "UPDATE_ROOT", self.root),
mock.patch.object(
desktop_updater, "UPDATE_STATE_FILE", self.root / "state.json"
),
mock.patch.object(
desktop_updater, "UPDATE_RESULT_FILE", self.root / "result.json"
),
]
for patcher in self.patches:
patcher.start()
def tearDown(self) -> None:
for patcher in reversed(self.patches):
patcher.stop()
self.temporary.cleanup()
def test_manifest_respects_server_eligibility_and_semantic_versions(self) -> None:
response = {
"latest_version": "2.0.0",
"download_url": "https://dl.example.com/update.exe",
"package_sha256": "a" * 64,
"package_size": 123,
"auto_install": True,
"eligible": False,
"update_available": False,
}
with mock.patch.object(
backend_client, "_request", return_value=(200, response)
) as request:
manifest = desktop_updater.fetch_manifest(
"https://admin.example.com", "device-1", current_version="1.1.2"
)
self.assertFalse(manifest["eligible"])
self.assertFalse(manifest["update_available"])
self.assertFalse(manifest["auto_install"])
self.assertIn("current_version=1.1.2", request.call_args.args[2])
def test_download_is_kept_only_after_sha256_verification(self) -> None:
content = b"MZ" + b"verified-installer" * 100
progress = []
manifest = {
"latest_version": "2.0.0",
"download_url": "http://127.0.0.1:8766/update.exe",
"package_sha256": hashlib.sha256(content).hexdigest(),
"package_size": len(content),
"signature_required": False,
}
with (
mock.patch.object(
desktop_updater.urllib.request,
"urlopen",
return_value=_DownloadResponse(content),
),
mock.patch.object(
desktop_updater, "authenticode_status", return_value="NotSigned"
),
):
installer = desktop_updater.download_update(
manifest, lambda downloaded, total: progress.append((downloaded, total))
)
self.assertEqual(installer.read_bytes(), content)
self.assertTrue(progress)
self.assertTrue((self.root / "state.json").is_file())
def test_bad_hash_is_deleted_and_never_becomes_an_installer(self) -> None:
content = b"MZ-corrupted"
manifest = {
"latest_version": "2.0.0",
"download_url": "http://localhost/update.exe",
"package_sha256": "0" * 64,
"package_size": len(content),
}
with mock.patch.object(
desktop_updater.urllib.request,
"urlopen",
return_value=_DownloadResponse(content),
):
with self.assertRaises(desktop_updater.UpdateError):
desktop_updater.download_update(manifest)
version_dir = self.root / "2.0.0"
self.assertFalse((version_dir / "ZhenAI-Update-v2.0.0.exe").exists())
self.assertFalse((version_dir / "ZhenAI-Update-v2.0.0.exe.part").exists())
def test_plain_http_is_rejected_for_remote_servers(self) -> None:
with self.assertRaises(desktop_updater.UpdateError):
desktop_updater._validated_download_url(
"http://downloads.example.com/update.exe"
)
def test_source_checkout_never_overwrites_an_installation(self) -> None:
installer = self.root / "update.exe"
installer.write_bytes(b"MZ")
with mock.patch.object(desktop_updater.sys, "frozen", False, create=True):
with self.assertRaises(desktop_updater.UpdateError):
desktop_updater.launch_installer(installer, "2.0.0")
if __name__ == "__main__":
unittest.main()