import hashlib import io import tempfile import unittest import zipfile from pathlib import Path from types import SimpleNamespace from unittest.mock import patch from host.main import NativeHost from host.updates import extract_zip, fetch, python_release, trusted_url class UpdateTests(unittest.TestCase): def test_automatic_checks_wait_for_idle_and_respect_disabled_setting(self): with tempfile.TemporaryDirectory() as temporary, patch("host.main.ROOT", Path(temporary)): host = NativeHost(io.BytesIO()) try: with patch("host.main.installation_root", return_value=Path(temporary)), patch.object(host, "schedule_updates"), \ patch("host.updates.settings", return_value={"enabled": True}) as settings, patch("host.updates.due", return_value=True), \ patch("host.updates.check_updates", return_value=[]) as check: host.jobs["video"] = {"status": "running"} host.auto_check(); check.assert_not_called() host.jobs.clear(); host.auto_check(); check.assert_called_once() settings.return_value = {"enabled": False}; check.reset_mock() host.auto_check(); check.assert_not_called() finally: host.close() def test_vendor_urls_and_archive_paths_are_restricted(self): trusted_url("https://nodejs.org/dist/v24.21.0/node-v24.21.0-win-x64.zip") for url in ("http://nodejs.org/file", "https://nodejs.org.evil.test/file", "https://name:secret@nodejs.org/file"): with self.assertRaises(ValueError): trusted_url(url) with tempfile.TemporaryDirectory() as temporary: root = Path(temporary) for name in ("../outside.exe", "C:/outside.exe", "runtime/../../outside.exe"): archive = root / "bad.zip" with zipfile.ZipFile(archive, "w") as package: package.writestr(name, b"bad") with self.assertRaises(ValueError): extract_zip(archive, root / "component") self.assertFalse((root / "outside.exe").exists()) def test_modified_download_is_rejected_before_use(self): class Response: headers = {"Content-Length": "8"} def __enter__(self): return self def __exit__(self, *args): return False def iter_content(self, count): yield b"modified" with tempfile.TemporaryDirectory() as temporary, patch("host.updates.get", return_value=Response()): item = {"url": "https://nodejs.org/file", "sha256": hashlib.sha256(b"original").hexdigest()} with self.assertRaisesRegex(ValueError, "Контрольная сумма"): fetch(item, Path(temporary) / "download.zip", lambda _: None) def test_python_uses_official_hash_and_stays_in_compatible_branch(self): checksum = "a" * 64 index = '3.14161514' def response(url): if url.endswith("/ftp/python/"): return SimpleNamespace(text=index) if "31316" in url: return SimpleNamespace(text="
{checksum}