mirror of
https://github.com/HKUDS/nanobot.git
synced 2026-08-31 16:21:50 +03:00
fix(release): package TUI compliance materials
This commit is contained in:
+118
-17
@@ -1,5 +1,7 @@
|
||||
import hashlib
|
||||
import io
|
||||
import subprocess
|
||||
import zipfile
|
||||
from pathlib import Path
|
||||
from types import SimpleNamespace
|
||||
|
||||
@@ -24,6 +26,32 @@ from nanobot.cli.tui_launcher import (
|
||||
from nanobot.config.schema import Config, ModelPresetConfig
|
||||
|
||||
|
||||
def _release_archive(
|
||||
asset: str,
|
||||
*,
|
||||
binary: bytes = b"native-tui",
|
||||
omit: str | None = None,
|
||||
) -> tuple[bytes, bytes]:
|
||||
files = {
|
||||
asset: binary,
|
||||
**{name: f"contents of {name}\n".encode() for name in tui_launcher._TUI_RELEASE_FILES},
|
||||
}
|
||||
if omit:
|
||||
files.pop(omit)
|
||||
manifest = "".join(
|
||||
f"{hashlib.sha256(content).hexdigest()} {name}\n" for name, content in files.items()
|
||||
).encode()
|
||||
files["MANIFEST.sha256"] = manifest
|
||||
output = io.BytesIO()
|
||||
with zipfile.ZipFile(output, "w", compression=zipfile.ZIP_DEFLATED) as archive:
|
||||
for name, content in files.items():
|
||||
archive.writestr(name, content)
|
||||
payload = output.getvalue()
|
||||
archive_name = f"{asset}.zip"
|
||||
checksum = f"{hashlib.sha256(payload).hexdigest()} {archive_name}\n".encode()
|
||||
return payload, checksum
|
||||
|
||||
|
||||
def test_authenticated_ws_url_preserves_existing_query(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
monkeypatch.setattr("nanobot.cli.tui_launcher.os.getpid", lambda: 42)
|
||||
url = _authenticated_ws_url(
|
||||
@@ -374,26 +402,28 @@ def test_release_tui_is_verified_and_cached(
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
tmp_path: Path,
|
||||
) -> None:
|
||||
asset = "nanobot-tui-linux-x64"
|
||||
binary = b"native-tui"
|
||||
digest = hashlib.sha256(binary).hexdigest().encode()
|
||||
archive, checksum = _release_archive(asset, binary=binary)
|
||||
downloads: list[str] = []
|
||||
|
||||
def read_asset(url: str, *, max_bytes: int) -> bytes:
|
||||
downloads.append(url)
|
||||
return digest if url.endswith(".sha256") else binary
|
||||
return checksum if url.endswith(".sha256") else archive
|
||||
|
||||
monkeypatch.setattr("nanobot.cli.tui_launcher.__version__", "9.9.9")
|
||||
monkeypatch.setattr("nanobot.cli.tui_launcher.get_data_dir", lambda: tmp_path)
|
||||
monkeypatch.setattr("nanobot.cli.tui_launcher._read_release_asset", read_asset)
|
||||
|
||||
target = _download_release_tui("nanobot-tui-linux-x64")
|
||||
target = _download_release_tui(asset)
|
||||
|
||||
assert target == tmp_path / "bin" / "tui" / "9.9.9" / "nanobot-tui-linux-x64"
|
||||
assert target == tmp_path / "bin" / "tui" / "9.9.9" / asset
|
||||
assert target.read_bytes() == binary
|
||||
assert target.with_name(f"{target.name}.sha256").read_text().startswith(digest.decode())
|
||||
for name in (*tui_launcher._TUI_RELEASE_FILES, "MANIFEST.sha256"):
|
||||
assert (target.parent / name).is_file()
|
||||
assert len(downloads) == 2
|
||||
|
||||
assert _download_release_tui("nanobot-tui-linux-x64") == target
|
||||
assert _download_release_tui(asset) == target
|
||||
assert len(downloads) == 2
|
||||
|
||||
|
||||
@@ -402,42 +432,113 @@ def test_release_tui_replaces_a_corrupted_cached_binary(
|
||||
tmp_path: Path,
|
||||
) -> None:
|
||||
binary = b"native-tui"
|
||||
digest = hashlib.sha256(binary).hexdigest().encode()
|
||||
asset = "nanobot-tui-linux-x64"
|
||||
target = tmp_path / "bin" / "tui" / "9.9.9" / asset
|
||||
target.parent.mkdir(parents=True)
|
||||
target.write_bytes(b"corrupted")
|
||||
target.with_name(f"{target.name}.sha256").write_text(
|
||||
f"{hashlib.sha256(binary).hexdigest()} {asset}\n"
|
||||
)
|
||||
archive, checksum = _release_archive(asset, binary=binary)
|
||||
downloads: list[str] = []
|
||||
|
||||
def read_asset(url: str, *, max_bytes: int) -> bytes:
|
||||
downloads.append(url)
|
||||
return digest if url.endswith(".sha256") else binary
|
||||
return checksum if url.endswith(".sha256") else archive
|
||||
|
||||
monkeypatch.setattr("nanobot.cli.tui_launcher.__version__", "9.9.9")
|
||||
monkeypatch.setattr("nanobot.cli.tui_launcher.get_data_dir", lambda: tmp_path)
|
||||
monkeypatch.setattr("nanobot.cli.tui_launcher._read_release_asset", read_asset)
|
||||
|
||||
target = _download_release_tui(asset)
|
||||
assert target is not None
|
||||
target.write_bytes(b"corrupted")
|
||||
|
||||
assert _download_release_tui(asset) == target
|
||||
assert target.read_bytes() == binary
|
||||
assert len(downloads) == 2
|
||||
assert len(downloads) == 4
|
||||
|
||||
|
||||
def test_release_tui_replaces_corrupted_cached_notices(
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
tmp_path: Path,
|
||||
) -> None:
|
||||
asset = "nanobot-tui-linux-x64"
|
||||
archive, checksum = _release_archive(asset)
|
||||
downloads: list[str] = []
|
||||
|
||||
def read_asset(url: str, *, max_bytes: int) -> bytes:
|
||||
downloads.append(url)
|
||||
return checksum if url.endswith(".sha256") else archive
|
||||
|
||||
monkeypatch.setattr("nanobot.cli.tui_launcher.__version__", "9.9.9")
|
||||
monkeypatch.setattr("nanobot.cli.tui_launcher.get_data_dir", lambda: tmp_path)
|
||||
monkeypatch.setattr("nanobot.cli.tui_launcher._read_release_asset", read_asset)
|
||||
|
||||
target = _download_release_tui(asset)
|
||||
assert target is not None
|
||||
notices = target.parent / "THIRD_PARTY_NOTICES.txt"
|
||||
notices.write_text("corrupted", encoding="utf-8")
|
||||
|
||||
assert _download_release_tui(asset) == target
|
||||
assert notices.read_bytes() == b"contents of THIRD_PARTY_NOTICES.txt\n"
|
||||
assert len(downloads) == 4
|
||||
|
||||
|
||||
def test_release_tui_rejects_bad_checksum(
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
tmp_path: Path,
|
||||
) -> None:
|
||||
asset = "nanobot-tui-linux-x64"
|
||||
archive, _checksum = _release_archive(asset)
|
||||
monkeypatch.setattr("nanobot.cli.tui_launcher.__version__", "9.9.9")
|
||||
monkeypatch.setattr("nanobot.cli.tui_launcher.get_data_dir", lambda: tmp_path)
|
||||
monkeypatch.setattr(
|
||||
"nanobot.cli.tui_launcher._read_release_asset",
|
||||
lambda url, *, max_bytes: b"0" * 64 if url.endswith(".sha256") else b"tampered",
|
||||
lambda url, *, max_bytes: (
|
||||
f"{'0' * 64} {asset}.zip\n".encode() if url.endswith(".sha256") else archive
|
||||
),
|
||||
)
|
||||
|
||||
with pytest.raises(TuiUnavailableError, match="checksum"):
|
||||
_download_release_tui("nanobot-tui-linux-x64")
|
||||
_download_release_tui(asset)
|
||||
|
||||
|
||||
def test_release_tui_rejects_an_archive_without_required_notices(
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
tmp_path: Path,
|
||||
) -> None:
|
||||
asset = "nanobot-tui-linux-x64"
|
||||
archive, checksum = _release_archive(asset, omit="THIRD_PARTY_NOTICES.txt")
|
||||
monkeypatch.setattr("nanobot.cli.tui_launcher.__version__", "9.9.9")
|
||||
monkeypatch.setattr("nanobot.cli.tui_launcher.get_data_dir", lambda: tmp_path)
|
||||
monkeypatch.setattr(
|
||||
"nanobot.cli.tui_launcher._read_release_asset",
|
||||
lambda url, *, max_bytes: checksum if url.endswith(".sha256") else archive,
|
||||
)
|
||||
|
||||
with pytest.raises(TuiUnavailableError, match="archive is incomplete"):
|
||||
_download_release_tui(asset)
|
||||
|
||||
|
||||
def test_release_tui_rejects_an_empty_required_file(
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
tmp_path: Path,
|
||||
) -> None:
|
||||
asset = "nanobot-tui-linux-x64"
|
||||
archive, _checksum = _release_archive(asset)
|
||||
source = io.BytesIO(archive)
|
||||
output = io.BytesIO()
|
||||
with zipfile.ZipFile(source) as original, zipfile.ZipFile(output, "w") as rebuilt:
|
||||
for entry in original.infolist():
|
||||
content = b"" if entry.filename == "SOURCE_OFFER.md" else original.read(entry)
|
||||
rebuilt.writestr(entry.filename, content)
|
||||
payload = output.getvalue()
|
||||
checksum = f"{hashlib.sha256(payload).hexdigest()} {asset}.zip\n".encode()
|
||||
|
||||
monkeypatch.setattr("nanobot.cli.tui_launcher.__version__", "9.9.9")
|
||||
monkeypatch.setattr("nanobot.cli.tui_launcher.get_data_dir", lambda: tmp_path)
|
||||
monkeypatch.setattr(
|
||||
"nanobot.cli.tui_launcher._read_release_asset",
|
||||
lambda url, *, max_bytes: checksum if url.endswith(".sha256") else payload,
|
||||
)
|
||||
|
||||
with pytest.raises(TuiUnavailableError, match="invalid size"):
|
||||
_download_release_tui(asset)
|
||||
|
||||
|
||||
def test_gateway_reuse_requires_the_matching_managed_instance(
|
||||
|
||||
Reference in New Issue
Block a user