import io import hashlib import json from pathlib import Path import tarfile import tempfile import unittest from intelligence_radar.advisor import recommend, replay from intelligence_radar.bundles import export_bundle, import_bundle from intelligence_radar.ingest import capture from intelligence_radar.store import canonical, get, put REPO = Path(__file__).resolve().parents[1] class BundleTests(unittest.TestCase): def setUp(self): self.temp = tempfile.TemporaryDirectory() self.addCleanup(self.temp.cleanup) self.root = Path(self.temp.name) self.source = self.root/'source' task = json.loads((REPO/'examples/task.json').read_text()) catalog = json.loads((REPO/'examples/advisor-catalog.json').read_text()) policy = json.loads((REPO/'policies/advisor-v0.2.json').read_text()) self.ident,self.report = recommend(self.source,task,catalog,policy,'2026-09-13',True) self.bundle=self.root/'report.tar' def test_portable_replay_and_idempotent_import(self): export_bundle(self.source,self.ident,self.bundle) target=self.root/'imported' import_bundle(target,self.bundle) self.assertEqual(replay(target,self.ident),self.report) import_bundle(target,self.bundle) export_bundle(self.source,self.ident,self.root/'second.tar') self.assertEqual(self.bundle.read_bytes(),(self.root/'second.tar').read_bytes()) def test_path_traversal_is_rejected_before_writes(self): with tarfile.open(self.bundle,'w') as archive: member=tarfile.TarInfo('../escape');member.size=1 archive.addfile(member,io.BytesIO(b'x')) with self.assertRaisesRegex(ValueError,'path'): import_bundle(self.root/'target',self.bundle) self.assertFalse((self.root/'escape').exists()) self.assertFalse((self.root/'target').exists()) def test_tampered_member_is_rejected_before_writes(self): export_bundle(self.source,self.ident,self.bundle) changed=self.root/'tampered.tar' with tarfile.open(self.bundle) as original, tarfile.open(changed,'w') as archive: for member in original: blob=original.extractfile(member).read() if member.name!='manifest.json':blob+=b' ' member.size=len(blob) archive.addfile(member,io.BytesIO(blob)) with self.assertRaisesRegex(ValueError,'checksum'): import_bundle(self.root/'target',changed) self.assertFalse((self.root/'target').exists()) def test_invalid_size_override_rejected(self): source={'id':'x','publisher':'x','url':'https://example.invalid','max_bytes':True} with self.assertRaises(ValueError): capture(self.source,source) def test_missing_dependency_is_rejected_even_when_replay_ignores_it(self): snapshot = get(self.source, 'recommendations', self.ident) snapshot['provenance'] = {'capture_id': 'a' * 64} ident = put(self.source, 'recommendations', snapshot) # Synthetic replay does not consult this provenance, but a portable # bundle must still carry every dependency it claims to retain. name = f'data/recommendations/{ident}.json' blob = (self.source / name).read_bytes() manifest = {'schema_version': 1, 'recommendation_id': ident, 'files': {name: hashlib.sha256(blob).hexdigest()}} with tarfile.open(self.bundle, 'w') as archive: for path, data in [(name, blob), ('manifest.json', canonical(manifest))]: member = tarfile.TarInfo(path) member.size = len(data) archive.addfile(member, io.BytesIO(data)) with self.assertRaises(FileNotFoundError): import_bundle(self.root / 'target', self.bundle) self.assertFalse((self.root / 'target').exists())