import json import io from pathlib import Path import subprocess import sys import tempfile import tarfile import unittest from unittest.mock import patch ROOT = Path(__file__).resolve().parents[2] sys.path.insert(0, str(ROOT / 'scripts')) from public_app_policy import check_catalog, check_member, copy_public_tree, public_catalog import release_payload class PublicAppPolicyTests(unittest.TestCase): def test_storefront_and_both_catalog_formats(self): for apps in ([{'id': 'kithmoot'}, {'id': 'node-demo-v4v'}, {'id': 'podsteadr'}], {'kithmoot': {}, 'v4v': {}, 'podsteadr': {}}): original = {'apps': apps, 'storefront': {'popular': ['kithmoot', 'v4v', 'podsteadr']}} with self.assertRaises(ValueError): check_catalog(original) filtered = public_catalog(original) check_catalog(filtered) self.assertEqual(filtered['storefront']['popular'], ['podsteadr']) self.assertIn('podsteadr', json.dumps(filtered)) def test_payload_paths_and_embedded_catalog(self): for app in ('kithmoot', 'v4v', 'node-demo-v4v'): for folder in ('apps', 'docker'): with self.assertRaises(ValueError): check_member(f'archipelago-runtime/{folder}/{app}/manifest.yml') with self.assertRaises(ValueError): check_member('catalog.json', json.dumps({'apps': {app: {}}}).encode()) check_member('archipelago-runtime/apps/podsteadr/manifest.yml') def test_iso_copy_excludes_private_apps_and_preserves_sources(self): with tempfile.TemporaryDirectory() as tmp: source, dest = Path(tmp) / 'apps', Path(tmp) / 'stage' for app in ('kithmoot', 'node-demo-v4v', 'v4v', 'podsteadr'): path = source / app / 'manifest.yml' path.parent.mkdir(parents=True) path.write_text(app) copy_public_tree(source, dest) self.assertEqual([p.name for p in dest.iterdir()], ['podsteadr']) self.assertEqual(len(list(source.iterdir())), 4) with self.assertRaises(ValueError): copy_public_tree(source, dest) def test_real_registry_generator_does_not_reintroduce_manifest(self): with tempfile.TemporaryDirectory() as tmp: output = Path(tmp) / 'catalog.json' subprocess.run(['bash', str(ROOT / 'scripts/generate-app-catalog.sh'), str(output)], check=True, capture_output=True) catalog = json.loads(output.read_text()) check_catalog(catalog) self.assertNotIn('kithmoot', catalog['apps']) self.assertIn('podsteadr', catalog['apps']) def test_archive_verifier_rejects_private_content_before_missing_payload(self): # Exercise the actual streaming verifier, not just the policy helper. with tempfile.TemporaryDirectory() as tmp: root = Path(tmp) for name, raw in ( ('catalog.json', b'{"apps":{"kithmoot":{}}}'), ('archipelago-runtime/apps/node-demo-v4v/manifest.yml', b'app: {}'), ): archive = root / 'payload.tar.gz' with tarfile.open(archive, 'w:gz') as package: info = tarfile.TarInfo(name) info.mode = 0o644 info.size = len(raw) package.addfile(info, io.BytesIO(raw)) with patch.object(release_payload, 'runtime_sources', return_value={}), \ patch.object(release_payload, 'regular', return_value=b''), \ self.assertRaisesRegex(ValueError, 'private test app'): release_payload.check_archive(root, archive, '2.0.3-alpha', '0' * 40) if __name__ == '__main__': unittest.main()