65 lines
3.6 KiB
Python
65 lines
3.6 KiB
Python
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 = '<a href="3.14.0/">3.14</a><a href="3.13.16/">16</a><a href="3.13.15/">15</a><a href="3.13.14/">14</a>'
|
|
def response(url):
|
|
if url.endswith("/ftp/python/"): return SimpleNamespace(text=index)
|
|
if "31316" in url: return SimpleNamespace(text="<tr><td>Source only</td></tr>")
|
|
return SimpleNamespace(text=f'<tr><td><a href="python-3.13.15-embed-amd64.zip">Windows embed</a></td><td><code>{checksum}</code></td></tr>')
|
|
with patch("host.updates.sys.version_info", SimpleNamespace(major=3, minor=13)), patch("host.updates.sys.version", "3.13.14"), patch("host.updates.get", side_effect=response):
|
|
result = python_release()
|
|
self.assertEqual(result["version"], "3.13.15"); self.assertEqual(result["sha256"], checksum)
|
|
|
|
|
|
if __name__ == "__main__": unittest.main()
|