#!/usr/bin/env python3 import sys import tempfile import unittest from pathlib import Path PROJECT_ROOT = Path(__file__).resolve().parents[1] SDK_SRC = PROJECT_ROOT / "packages" / "jwebot-sdk" / "src" KERNEL_SRC = PROJECT_ROOT / "packages" / "jwebot-kernel" / "src" sys.path[:0] = [str(SDK_SRC), str(KERNEL_SRC)] from jwebot_kernel import ( # noqa: E402 ManifestError, PluginType, load_manifest, parse_manifest, ) from jwebot_kernel import manifest as manifest_module # noqa: E402 class ManifestTests(unittest.TestCase): def manifest(self, **overrides): value = { "id": "hello-command", "author": "jwebot", "description": "Say hello", "help": ["/hello [name]"], "version": "1.0.0", "api_version": "1", "entrypoint": "hello_command:plugin", "type": "command", "capabilities": { "commands": ["/hello"], "storage": False, "network_profiles": [], "service_profiles": [], }, } value.update(overrides) return value def test_command_manifest_is_parsed(self): manifest = parse_manifest(self.manifest()) self.assertEqual(manifest.plugin_id, "hello-command") self.assertEqual(manifest.author, "jwebot") self.assertEqual(manifest.description, "Say hello") self.assertEqual(manifest.help_entries, ("/hello [name]",)) self.assertEqual(manifest.plugin_type, PluginType.COMMAND) self.assertEqual(manifest.capabilities.commands, ("/hello",)) def test_author_is_required_and_bounded(self): value = self.manifest() del value["author"] with self.assertRaisesRegex(ManifestError, "author must"): parse_manifest(value) with self.assertRaisesRegex(ManifestError, "author has an invalid format"): parse_manifest(self.manifest(author="../Alice")) def test_unknown_capability_is_rejected(self): value = self.manifest() value["capabilities"]["shell"] = True with self.assertRaisesRegex(ManifestError, "unknown capabilities"): parse_manifest(value) def test_description_and_help_are_declared_together(self): value = self.manifest() del value["help"] with self.assertRaisesRegex( ManifestError, "declared together", ): parse_manifest(value) def test_command_manifest_requires_visible_help_metadata(self): value = self.manifest() del value["description"] del value["help"] with self.assertRaisesRegex( ManifestError, "must declare description", ): parse_manifest(value) def test_help_entries_are_bounded_command_usage(self): with self.assertRaisesRegex(ManifestError, "invalid display"): parse_manifest(self.manifest(help=["plain prose"])) with self.assertRaisesRegex(ManifestError, "display boundary"): parse_manifest(self.manifest(description="x" * 121)) def test_command_extension_requires_a_namespace(self): value = self.manifest() value["capabilities"]["commands"] = [] with self.assertRaisesRegex(ManifestError, "at least one command"): parse_manifest(value) def test_non_command_extension_cannot_claim_a_command(self): with self.assertRaisesRegex(ManifestError, "only command extensions"): parse_manifest(self.manifest(type="enrichment")) def test_unsupported_api_version_is_rejected(self): with self.assertRaisesRegex(ManifestError, "unsupported api_version"): parse_manifest(self.manifest(api_version="2")) def test_entrypoint_must_use_plugin_namespace(self): with self.assertRaisesRegex(ManifestError, "plugin module namespace"): parse_manifest(self.manifest(entrypoint="json:plugin")) def test_service_profiles_are_bounded_and_parsed(self): value = self.manifest() value["capabilities"]["service_profiles"] = ["coin"] self.assertEqual( parse_manifest(value).capabilities.service_profiles, ("coin",), ) def test_duplicate_commands_are_rejected(self): value = self.manifest() value["capabilities"]["commands"] = ["/hello", "/hello"] with self.assertRaisesRegex(ManifestError, "duplicates"): parse_manifest(value) def test_case_insensitive_duplicate_commands_are_rejected(self): value = self.manifest() value["capabilities"]["commands"] = ["/Hello", "/hello"] with self.assertRaisesRegex(ManifestError, "case-insensitive duplicates"): parse_manifest(value) def test_command_rejects_manifest_breaking_characters(self): value = self.manifest() value["capabilities"]["commands"] = ['/"quoted'] with self.assertRaisesRegex(ManifestError, "invalid value"): parse_manifest(value) def test_command_accepts_chinese_names(self): value = self.manifest() value["capabilities"]["commands"] = ["/天气"] self.assertEqual( parse_manifest(value).capabilities.commands, ("/天气",), ) def test_load_manifest_reads_toml(self): if manifest_module.tomllib is None: self.skipTest("tomli is not installed under local Python") with tempfile.TemporaryDirectory() as directory: path = Path(directory) / "plugin.toml" path.write_text( "\n".join( ( 'id = "hello-command"', 'author = "Alice"', 'description = "Say hello"', 'help = ["/hello"]', 'version = "1.0.0"', 'api_version = "1"', 'entrypoint = "hello_command:plugin"', 'type = "command"', "", "[capabilities]", 'commands = ["/hello"]', "storage = false", "network_profiles = []", ) ), encoding="utf-8", ) self.assertEqual(load_manifest(path).plugin_id, "hello-command") if __name__ == "__main__": unittest.main()