import json import hashlib import shutil import tempfile import unittest import zipfile from pathlib import Path from tools.packages import PackageError, check_package, package_archive ROOT = Path(__file__).resolve().parents[1] class PackageTests(unittest.TestCase): def test_release_package_and_starter_are_valid(self): for path in (ROOT / "packages/planner-scope", ROOT / "examples/starter-planner"): check_package(path) with tempfile.TemporaryDirectory() as directory: first = package_archive(path, Path(directory) / "one.zip") second = package_archive(path, Path(directory) / "two.zip") self.assertEqual(first, second) def test_rejects_scripts_and_undeclared_files(self): with tempfile.TemporaryDirectory() as directory: package = Path(directory) / "planner-scope" shutil.copytree(ROOT / "packages/planner-scope", package) (package / "helper.py").write_text("print('unsafe')\n") with self.assertRaises(PackageError): check_package(package) def test_rejects_permission_expansion(self): with tempfile.TemporaryDirectory() as directory: package = Path(directory) / "planner-scope" shutil.copytree(ROOT / "packages/planner-scope", package) manifest = package / "relayview-skill.json" data = json.loads(manifest.read_text()) data["tools"] = ["shell"] manifest.write_text(json.dumps(data)) with self.assertRaises(PackageError): check_package(package) def test_rejects_role_mismatch(self): with tempfile.TemporaryDirectory() as directory: package = Path(directory) / "planner-scope" shutil.copytree(ROOT / "packages/planner-scope", package) manifest = package / "relayview-skill.json" data = json.loads(manifest.read_text()) data["role"] = "reviewer" manifest.write_text(json.dumps(data)) with self.assertRaises(PackageError): check_package(package) def test_rejects_changed_resource(self): with tempfile.TemporaryDirectory() as directory: package = Path(directory) / "planner-scope" shutil.copytree(ROOT / "packages/planner-scope", package) (package / "scope.md").write_text("changed\n") with self.assertRaises(PackageError): check_package(package) def test_rejects_frontmatter_permission_request(self): with tempfile.TemporaryDirectory() as directory: package = Path(directory) / "planner-scope" shutil.copytree(ROOT / "packages/planner-scope", package) skill = package / "SKILL.md" skill.write_text(skill.read_text().replace("metadata:\n", "tools: [shell]\nmetadata:\n")) manifest = package / "relayview-skill.json" data = json.loads(manifest.read_text()) next(item for item in data["files"] if item["path"] == "SKILL.md")["sha256"] = hashlib.sha256(skill.read_bytes()).hexdigest() manifest.write_text(json.dumps(data)) with self.assertRaises(PackageError): check_package(package) def test_rejects_owner_incompatible_frontmatter_values(self): for old, new in ( ("metadata:\n", "disable-model-invocation: yes\nmetadata:\n"), ("description: Check a Planner's issue scope and evidence before drafting a Plan.", "description: "), ): with self.subTest(new=new), tempfile.TemporaryDirectory() as directory: package = Path(directory) / "planner-scope" shutil.copytree(ROOT / "packages/planner-scope", package) skill = package / "SKILL.md" skill.write_text(skill.read_text().replace(old, new)) manifest = package / "relayview-skill.json" data = json.loads(manifest.read_text()) next(item for item in data["files"] if item["path"] == "SKILL.md")["sha256"] = hashlib.sha256(skill.read_bytes()).hexdigest() manifest.write_text(json.dumps(data)) with self.assertRaises(PackageError): check_package(package) def test_archive_carries_license_and_notice(self): with tempfile.TemporaryDirectory() as directory: archive_path = Path(directory) / "planner-scope.zip" package_archive(ROOT / "packages/planner-scope", archive_path) with zipfile.ZipFile(archive_path) as archive: self.assertEqual(archive.read("LICENSE"), (ROOT / "LICENSE").read_bytes()) self.assertEqual(archive.read("NOTICE.md"), (ROOT / "NOTICE.md").read_bytes()) if __name__ == "__main__": unittest.main()