"""Disposable artifact tests: exercise byte validation, never privileged runtime.""" import ast import importlib.util import io import json import os from pathlib import Path import shutil import struct import subprocess import sys import tarfile import tempfile import unittest SCRIPTS = Path(__file__).resolve().parents[1] sys.path.insert(0, str(SCRIPTS)) import release_payload as payload VERSION = '2.0.1-alpha' COMMIT = 'a' * 40 class PayloadTests(unittest.TestCase): def setUp(self): self.tmp = tempfile.TemporaryDirectory() self.addCleanup(self.tmp.cleanup) self.root = Path(self.tmp.name) self.source = self.root / 'source' self.dist = self.root / 'dist' for name, raw in { 'scripts/d09_handoff.py': b'handoff', 'scripts/c01_network_owner.py': b'network', 'scripts/host_future_capability.py': b'new typed helper', 'image-recipe/configs/archipelago-c01-network.service': b'unit', 'neode-ui/public/packages/archipelago-companion.apk': b'qualified APK bytes', 'neode-ui/public/packages/archipelago-companion.json': b'{"versionCode":58,"versionName":"0.5.38-debug"}', }.items(): path = self.source / name path.parent.mkdir(parents=True, exist_ok=True) path.write_bytes(raw) payload.stage(self.source, self.dist, VERSION, COMMIT) for name, raw in { 'index.html': b'dashboard', 'aiui/index.html': b'AIUI', 'aiui/BUILD-INFO': f'commit={COMMIT}\nbase_path=/aiui/\n'.encode(), **{'packages/' + p.name: p.read_bytes() for p in (self.source / 'neode-ui/public/packages').iterdir()}, }.items(): path = self.dist / name path.parent.mkdir(parents=True, exist_ok=True) path.write_bytes(raw) def archive(self, omit=None, duplicate=None, prefix='', link=None): path = self.root / 'frontend.tar.gz' with tarfile.open(path, 'w:gz') as tar: for source in sorted(self.dist.rglob('*')): name = source.relative_to(self.dist).as_posix() if name == omit: continue info = tar.gettarinfo(str(source), prefix + name) info.mode = 0o755 if source.is_dir() else 0o644 if name == link: info.type = tarfile.SYMTYPE info.linkname = '/etc/passwd' info.size = 0 tar.addfile(info, io.BytesIO(source.read_bytes()) if info.isreg() else None) if name == duplicate: tar.addfile(info, io.BytesIO(source.read_bytes())) return path def check(self, **kwargs): payload.check_archive(self.source, self.archive(**kwargs), VERSION, COMMIT) def test_complete_flat_archive(self): self.check() def test_staging_filters_private_apps_without_changing_source_or_public_apps(self): for domain in ('apps', 'docker'): for name in ('kithmoot', 'V4V', 'node-demo-v4v', 'public-app'): original = self.source / domain / name / 'retained.txt' original.parent.mkdir(parents=True) original.write_text('source must remain') staged = self.dist / 'archipelago-runtime' / domain / name / 'retained.txt' staged.parent.mkdir(parents=True) staged.write_bytes(original.read_bytes()) catalog = self.dist / 'catalog.json' catalog.write_text(json.dumps({'apps': [{'id': 'kithmoot'}, {'id': 'public-app'}], 'storefront': {'promotions': [{'appId': 'v4v'}]}})) payload.stage(self.source, self.dist, VERSION, COMMIT) for domain in ('apps', 'docker'): for name in ('kithmoot', 'V4V', 'node-demo-v4v'): self.assertFalse((self.dist / 'archipelago-runtime' / domain / name).exists()) self.assertEqual((self.source / domain / name / 'retained.txt').read_text(), 'source must remain') self.assertTrue((self.dist / 'archipelago-runtime' / domain / 'public-app/retained.txt').exists()) self.assertEqual(json.loads(catalog.read_bytes()), {'apps': [{'id': 'public-app'}], 'storefront': {'promotions': []}}) self.check() def test_new_helper_is_required_without_allowlist_update(self): (self.source / 'scripts/host_concurrent.py').write_text('new lane helper') with self.assertRaisesRegex(ValueError, 'missing payload'): self.check() def test_network_and_handoff_omissions_rejected(self): for name in ('c01_network_owner.py', 'd09_handoff.py', 'host_future_capability.py'): with self.subTest(name=name), self.assertRaisesRegex(ValueError, 'missing payload'): self.check(omit='archipelago-runtime/scripts/' + name) def test_stale_companion_metadata_and_apk_rejected(self): for name in ('archipelago-companion.apk', 'archipelago-companion.json'): path = self.dist / 'packages' / name original = path.read_bytes() path.write_bytes(b'old') with self.subTest(name=name), self.assertRaisesRegex(ValueError, 'source mismatch'): self.check() path.write_bytes(original) def test_duplicate_link_and_nested_layout_rejected(self): for kwargs in ({'duplicate': 'index.html'}, {'link': 'index.html'}, {'prefix': 'neode-ui/'}): with self.subTest(kwargs=kwargs), self.assertRaises(ValueError): self.check(**kwargs) def test_false_hash_declaration_rejected(self): path = self.dist / 'archipelago-runtime/RELEASE-PAYLOAD.json' value = json.loads(path.read_text()) value['version'] = '2.0.0-alpha' path.write_text(json.dumps(value)) with self.assertRaisesRegex(ValueError, 'declaration mismatch'): self.check() def test_aiui_previous_commit_rejected(self): (self.dist / 'aiui/BUILD-INFO').write_text('commit=' + 'b' * 40 + '\nbase_path=/aiui/\n') with self.assertRaisesRegex(ValueError, 'AIUI'): self.check() def test_wrong_native_architecture_and_script_rejected(self): path = self.root / 'backend' header = bytearray(64) header[:6] = b'\x7fELF\x02\x01' struct.pack_into('