"""Unit-Tests Plugin-Manifest-Validierung (PLAN.md §14, §27.2).""" from __future__ import annotations import json import zipfile from pathlib import Path from hms_plugin_sdk import load_manifest, validate_manifest, validate_plugin_zip REPO = Path(__file__).resolve().parents[2] EXAMPLES = REPO / "plugins" / "examples" def _valid_manifest() -> dict: return json.loads( (EXAMPLES / "com.hms.fx.example_passthrough" / "plugin.json").read_text(encoding="utf-8") ) def test_example_passthrough_validates_with_shaders() -> None: manifest, errors = load_manifest(EXAMPLES / "com.hms.fx.example_passthrough") assert errors == [], errors assert manifest["id"] == "com.hms.fx.example_passthrough" def test_example_gaussian_blur_validates_with_shaders() -> None: manifest, errors = load_manifest(EXAMPLES / "com.hms.fx.gaussian_blur") assert errors == [], errors variants = manifest["adaptive_quality"]["variants"] assert [v["id"] for v in variants] == ["low", "medium", "high"] assert [v["samples"] for v in variants] == [5, 9, 17] def test_valid_manifest_without_root_ok() -> None: assert validate_manifest(_valid_manifest()) == [] def test_wrong_schema_version_rejected() -> None: m = _valid_manifest() m["schema_version"] = 99 assert any("schema_version" in e for e in validate_manifest(m)) def test_invalid_plugin_id_rejected() -> None: m = _valid_manifest() m["id"] = "../evil" assert any("invalid plugin id" in e for e in validate_manifest(m)) def test_invalid_semver_rejected() -> None: m = _valid_manifest() m["version"] = "1.0" assert any("semantic" in e for e in validate_manifest(m)) def test_dmx_footprint_over_8_slots_rejected() -> None: m = _valid_manifest() m["parameters"] = [ {"id": f"p{i}", "label": f"P{i}", "type": "float", "minimum": 0, "maximum": 1, "default": 0, "dmx_slots": [i]} for i in range(1, 10) ] assert any("exceeds 8" in e for e in validate_manifest(m)) # §14.7 def test_unsafe_shader_path_rejected() -> None: m = _valid_manifest() m["entrypoints"]["gl"]["passes"][0]["fragment"] = "../../evil.frag" assert any("unsafe shader path" in e for e in validate_manifest(m)) def test_missing_shader_file_detected_with_root() -> None: m = _valid_manifest() m["entrypoints"]["gl"]["passes"][0]["fragment"] = "shaders/gl/missing.frag" errors = validate_manifest(m, plugin_root=EXAMPLES / "com.hms.fx.example_passthrough") assert any("missing shader file" in e for e in errors) def test_invalid_failure_mode_rejected() -> None: m = _valid_manifest() m["failure_mode"] = "crash" assert any("failure_mode" in e for e in validate_manifest(m)) def test_duplicate_parameter_ids_rejected() -> None: m = _valid_manifest() m["parameters"].append(dict(m["parameters"][0])) assert any("duplicate parameter id" in e for e in validate_manifest(m)) def _make_zip(tmp_path: Path, files: dict[str, str | bytes]) -> Path: zpath = tmp_path / "plugin.zip" with zipfile.ZipFile(zpath, "w") as zf: for name, content in files.items(): zf.writestr(name, content) return zpath def test_valid_zip_passes(tmp_path: Path) -> None: plugin_dir = EXAMPLES / "com.hms.fx.example_passthrough" files: dict[str, str | bytes] = {} for f in sorted(plugin_dir.rglob("*")): if f.is_file(): rel = f.relative_to(plugin_dir.parent) files[str(rel)] = f.read_text(encoding="utf-8") zpath = _make_zip(tmp_path, files) assert validate_plugin_zip(zpath) == [] def test_zip_traversal_rejected(tmp_path: Path) -> None: files = {"pkg/plugin.json": json.dumps(_valid_manifest()), "../evil.frag": "x"} zpath = _make_zip(tmp_path, files) assert any("unsafe path" in e for e in validate_plugin_zip(zpath)) def test_zip_disallowed_file_type_rejected(tmp_path: Path) -> None: files = { "pkg/plugin.json": json.dumps(_valid_manifest()), "pkg/evil.exe": "MZ", } zpath = _make_zip(tmp_path, files) assert any("disallowed file type" in e for e in validate_plugin_zip(zpath)) def test_zip_without_manifest_rejected(tmp_path: Path) -> None: zpath = _make_zip(tmp_path, {"pkg/shader.frag": "void main(){}"}) assert any("plugin.json not found" in e for e in validate_plugin_zip(zpath)) def test_corrupt_zip_rejected(tmp_path: Path) -> None: zpath = tmp_path / "broken.zip" zpath.write_bytes(b"not a zip at all") assert any("not a valid zip" in e for e in validate_plugin_zip(zpath))