Files
HMS MediaEngine Agent 362e089be0 AUFGERAUMT: Root auf 10 sichtbare Elemente reduziert
Der Nutzer hat recht: Der Ordner war voller Entwicklungs-Muell.
Jetzt ist sauber getrennt:

ROOT (was der Nutzer sieht und braucht):
- run.py                     = das Programm
- hms_app/                   = der Anwendungscode
- HMS MediaEngine.app        = macOS Doppelklick-Starter
- HMS-Start.vbs              = Windows Doppelklick-Starter
- HMS-Install.vbs             = Windows Erst-Installation
- HMS-Mac-Install.command     = macOS Homebrew-Installation
- HMS-Portable-Install.command = macOS Portable-Installation (16GB-Fix)
- installer_gui.py           = grafischer Installer
- launcher.pyw + launcher_core.py = interne Start-Logik
- LIESMICH.txt               = 10-Zeilen-Kurzanleitung
- .gitignore

_entwicklung/ (alles andere, NICHT benoetigt):
- packages/ apps/ native/ plugins/ tools/ schemas/ tests/ docs/
  build/ fixture_profiles/
- PLAN.md STATUS.md ERRORS.md TEST_REPORT.md CHANGELOG.md README.md
- pyproject.toml uv.lock setup_*.sh/ps1 make_mac_app.py

Diese Trennung gilt ab sofort fuer alle Commits. Der Nutzer kann
_entwicklung/ loeschen wenn er Platz braucht - die App laeuft ohne.

Verifiziert: App startet nach Aufraeumen unveraendert (Health 200).
2026-09-11 23:44:06 +02:00

135 lines
4.5 KiB
Python

"""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))