zapret-kvn/tests/test_update_store_step.py
loop-uh e28d92309f
Some checks failed
Windows project source guards / test (push) Failing after 27s
Отчёт об обновлении: ступень загрузки, объём и кто держал файл
После обновления программа сообщает серверу, какой ступенью скачала
(files — хранилище, parts — куски архива, setup — архив целиком) и
сколько; при неудаче «lock» — код программы, державшей файл. Ядро —
копия из Zapret 2 GUI.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 14:01:07 +03:00

106 lines
5.3 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

from __future__ import annotations
"""Лестница загрузки обновления: хранилище с заплатками → куски архива → архив целиком."""
import tempfile
import unittest
from pathlib import Path
from types import SimpleNamespace
from unittest.mock import patch
from xray_fluent.updates import app_updater
from zapret_update import manifest as update_manifest
def _manifest(*, with_store: bool):
return update_manifest.parse({
"schema": 1, "product": "zapret-kvn", "channel": "stable", "version": "9.9.9", "created": 1, "key": "k",
"files": [{"path": "ZapretKVN.exe", "size": 3, "sha256": "a" * 64, "zsize": 2 if with_store else 0}],
})
class StoreStepTests(unittest.TestCase):
def setUp(self) -> None:
self._tmp = tempfile.TemporaryDirectory()
self.addCleanup(self._tmp.cleanup)
self.app = Path(self._tmp.name) / "app"
self.app.mkdir()
def _downloader(self, manifest):
update = app_updater.AppUpdate("9.9.9", "v9.9.9", "https://git2.zapret.moe/x.zip", 100, "", "b" * 64, (), manifest)
return app_updater.UpdateDownloader(update, app_dir=self.app)
def test_release_without_a_store_skips_the_step(self) -> None:
with patch("zapret_update.fetch.fetch_plan") as fetch:
self.assertIsNone(self._downloader(_manifest(with_store=False))._try_store())
fetch.assert_not_called()
def test_store_builds_the_new_version_tree(self) -> None:
tree = self.app / "data" / "runtime" / "app_update" / "store-9.9.9" / "tree"
def build(planned, *, root, ready, stage_dir):
stage_dir.mkdir(parents=True)
(stage_dir / "ZapretKVN.exe").write_bytes(b"new")
return stage_dir
with patch("zapret_update.fetch.fetch_plan", return_value={}) as fetch, patch(
"zapret_update.stage.build_stage", side_effect=build
), patch("zapret_update.store.codecs", return_value=frozenset({"zstd"})):
self.assertEqual(self._downloader(_manifest(with_store=True))._try_store(), tree)
# Скачанное лежит в постоянной папке: после перезапуска загрузка продолжится.
self.assertEqual(fetch.call_args.kwargs["work_dir"], tree.parent)
self.assertIn("zapretkvn/generic/zapret-kvn-store/1/", fetch.call_args.kwargs["sources"][0].url_for("x.zst"))
def test_log_names_patch_kinds(self) -> None:
import hashlib
old = b"old"
(self.app / "ZapretKVN.exe").write_bytes(old)
listed = update_manifest.parse({
"schema": 1, "product": "zapret-kvn", "channel": "stable", "version": "9.9.9", "created": 1, "key": "k",
"files": [{"path": "ZapretKVN.exe", "size": 3, "sha256": "a" * 64, "zsize": 50}],
"patches": [{"from": hashlib.sha256(old).hexdigest(), "to": "a" * 64, "size": 7}],
})
def build(planned, *, root, ready, stage_dir):
stage_dir.mkdir(parents=True)
(stage_dir / "ZapretKVN.exe").write_bytes(b"new")
return stage_dir
with patch("zapret_update.fetch.fetch_plan", return_value={}), patch(
"zapret_update.stage.build_stage", side_effect=build
), patch("zapret_update.store.codecs", return_value=frozenset({"zstd"})), self.assertLogs(
app_updater._log, "INFO"
) as logs:
downloader = self._downloader(listed)
self.assertIsNotNone(downloader._try_store())
self.assertIn("скачано 7 из 100 байт (файлов 1, заплаток 1 (zstd 1), без изменений 0)", "\n".join(logs.output))
# Ступень и объём уходят серверу вместе с «дошла».
self.assertEqual((downloader.via, downloader.downloaded), ("files", 7))
def test_without_zstd_unpacker_nothing_is_downloaded(self) -> None:
# 0.8.26 собрана на Python 3.12 без zstandard: качала файл и падала на распаковке.
with patch("zapret_update.fetch.fetch_plan") as fetch, patch(
"zapret_update.store.codecs", return_value=frozenset({"bsdiff"})
):
self.assertIsNone(self._downloader(_manifest(with_store=True))._try_store())
fetch.assert_not_called()
def test_failed_store_hands_over_to_the_next_step(self) -> None:
from zapret_update.fetch import FetchError
with patch("zapret_update.fetch.fetch_plan", side_effect=FetchError("нет сети")) as fetch, patch(
"zapret_update.store.codecs", return_value=frozenset({"zstd"})
):
self.assertIsNone(self._downloader(_manifest(with_store=True))._try_store())
fetch.assert_called_once()
def test_store_comes_before_archive_pieces(self) -> None:
downloader = self._downloader(_manifest(with_store=True))
order = []
downloader._try_store = lambda: order.append("store") or None
downloader._try_delta = lambda work_dir: order.append("delta") or None
downloader._fetch = lambda archive: (order.append("zip"), (_ for _ in ()).throw(app_updater.DownloadFailed("стоп")))
with patch.object(app_updater, "_purge_stale_update_dirs"):
downloader.run()
self.assertEqual(order, ["store", "delta", "zip"])