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

113 lines
4.0 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""Unit-Tests Project-State-Store (PLAN.md §6.4 State Sync, §24.2, §18.1)."""
from __future__ import annotations
import pytest
from hms_domain import PresetScene
from hms_persistence.state_store import ProjectStateStore
def test_revisions_start_at_zero() -> None:
store = ProjectStateStore()
assert store.state_revision == 0
assert store.project_revision == 0
assert store.snapshot()["values"] == {}
def test_activate_project_resets_live_state() -> None:
"""Kein Mischzustand: Projektwechsel leert den Livezustand (§24.2)."""
from hms_domain import Project
store = ProjectStateStore()
store.set_value("master/intensity", 0.5)
store.activate_project(Project(name="Show A"))
assert store.project_revision == 1
assert store.project is not None and store.project.name == "Show A"
assert store.get_value("master/intensity") is None # Live geleert
assert store.state_revision == 2 # set_value (1) + Projektwechsel (2)
def test_set_and_clear_value_increase_state_revision() -> None:
store = ProjectStateStore()
r1 = store.set_value("master/intensity", 0.7)
assert r1 == 1
r2 = store.set_value("master/intensity", 0.9)
assert r2 == 2
r3 = store.clear_value("master/intensity")
assert r3 == 3
assert store.get_value("master/intensity") is None
# Löschen eines nicht existierenden Pfades erhöht trotzdem deterministisch
r4 = store.clear_value("master/intensity")
assert r4 == 4
def test_snapshot_contains_revisions_and_values() -> None:
store = ProjectStateStore()
store.set_value("master/intensity", 1.0)
snap = store.snapshot()
assert snap["state_revision"] == 1
assert snap["project_revision"] == 0
assert snap["values"] == {"master/intensity": 1.0}
assert snap["monotonic_ns"] > 0
def test_delta_since_reports_changes_and_deletions() -> None:
"""Delta überträgt Änderungen; gelöschte Pfade als None (§6.4)."""
store = ProjectStateStore()
store.set_value("a", 1.0)
seen = store.state_revision
# nach „Disconnect": neue Änderungen
store.set_value("a", 2.0)
store.set_value("b", 3.0)
store.clear_value("c") # c war nie gesetzt bleibt neutral
delta = store.delta_since(seen, pending={"a": 1.0, "c": 0.0})
assert delta is not None
assert delta.state_revision == store.state_revision
assert delta.changes["a"] == 2.0 # geändert
assert "b" in delta.changes and delta.changes["b"] == 3.0 # neu
assert delta.changes.get("c") is None # gelöscht
def test_delta_none_when_up_to_date() -> None:
store = ProjectStateStore()
store.set_value("a", 1.0)
assert store.delta_since(store.state_revision, pending={}) is None
def test_delta_rejects_future_revision() -> None:
store = ProjectStateStore()
with pytest.raises(ValueError, match="Zukunft"):
store.delta_since(99, pending={})
def test_apply_scene_overwrites_live_as_target_state() -> None:
"""Szenenabruf erzeugt Zielzustand; Übergänge macht der Renderer (§18.1)."""
store = ProjectStateStore()
store.set_value("layer/x/opacity", 0.2)
scene = PresetScene(
name="Look",
composition_snapshot={
"values": {"layer/x/opacity": 0.8, "master/intensity": 1.0}
},
)
rev = store.apply_scene(scene)
assert rev == 2 # set + scene
assert store.get_value("layer/x/opacity") == pytest.approx(0.8)
assert store.get_value("master/intensity") == pytest.approx(1.0)
def test_apply_scene_rejects_scene_without_values() -> None:
scene = PresetScene(name="Kaputt", composition_snapshot={"values": "kein dict"})
store = ProjectStateStore()
with pytest.raises(ValueError, match="Werte"):
store.apply_scene(scene)
def test_bump_project_revision_independent_of_state() -> None:
"""Projektinhalt (Medien/Szenen) und Livezustand getrennt (§24.2)."""
store = ProjectStateStore()
state_before = store.state_revision
store.bump_project_revision()
assert store.project_revision == 1
assert store.state_revision == state_before # Live unverändert