Files
hms-mediaengine/tests/unit/test_media_library.py
T
HMS MediaEngine Agent d24fe3a963 Phase 2: Medien-Engine – Transport, Preload, Library, Content-Manifest
- PlaybackController (§12.5): Play/Pause/Stop/Retrigger, Loop/Once/
  Ping-Pong mit In/Out-Fenstern, variable Geschwindigkeit ±4x (§16.6),
  Ende-Ereignis genau einmal je Pass, monotone Ereignis-Zeitbasis (§12.2)
- PreloadSlot (§12.2): atomarer Clipwechsel preload->ready->commit;
  nicht bereites Medium wechselt nie (Policy beim Aufrufer)
- MediaLibrary (§13.2/§13.3): Import mit SHA-256-Duplikaterkennung,
  Bank-Slots als explizite Show-Metadaten, fehlende Dateien markiert,
  Relink; Import laeuft im Control-Core-Worker, nie im Renderthread
- ContentManifest (§6.4/§10.1): SHA-256 je Datei + resumierbare
  Chunk-Hashes, Verifikation gegen Veraenderung, Diff fuer Sync-Plan,
  deterministische Serialisierung mit Revision
- 35 neue Unit-Tests; Gesamtsuite 258 gruen, Ruff gruen
2026-09-11 01:25:23 +02:00

90 lines
3.5 KiB
Python

"""Unit-Tests Medienbibliothek (PLAN.md §13.2, §13.3)."""
from __future__ import annotations
from pathlib import Path
import pytest
from hms_media import ImportStatus, MediaLibrary
@pytest.fixture()
def media_root(tmp_path: Path) -> Path:
root = tmp_path / "media"
(root / "clips").mkdir(parents=True)
(root / "clips" / "intro.mp4").write_bytes(b"video-bytes-1" * 20)
(root / "clips" / "loop.mp4").write_bytes(b"video-bytes-2" * 20)
return root
def test_import_creates_asset_with_hash(media_root: Path) -> None:
lib = MediaLibrary(media_root)
outcome = lib.import_file(media_root / "clips" / "intro.mp4")
assert outcome.status is ImportStatus.IMPORTED
assert outcome.asset is not None
assert outcome.asset.content_hash # §13.2: Hash vorhanden
assert outcome.asset.rel_path == "clips/intro.mp4"
assert len(lib) == 1
def test_duplicate_content_detected(media_root: Path) -> None:
"""Gleicher Inhalt unter anderem Namen = Duplikat (§13.3)."""
lib = MediaLibrary(media_root)
first = lib.import_file(media_root / "clips" / "intro.mp4")
copy = media_root / "clips" / "kopie.mp4"
copy.write_bytes((media_root / "clips" / "intro.mp4").read_bytes())
outcome = lib.import_file(copy)
assert outcome.status is ImportStatus.DUPLICATE
assert outcome.duplicate_of == first.asset.id # verweist auf das Original
assert len(lib) == 1 # kein zweiter Eintrag
def test_import_missing_file_reports_cleanly(media_root: Path) -> None:
lib = MediaLibrary(media_root)
outcome = lib.import_file(media_root / "clips" / "fehlt.mp4")
assert outcome.status is ImportStatus.MISSING_FILE
assert outcome.asset is None
def test_bank_slots_are_explicit_metadata(media_root: Path) -> None:
"""Bank/Index als Show-Metadaten, unabhängig vom Dateinamen (§13.2)."""
lib = MediaLibrary(media_root)
asset = lib.import_file(media_root / "clips" / "intro.mp4").asset
lib.register_bank_slot(asset.id, bank=1, index=5)
found = lib.asset_at(bank=1, index=5)
assert found is not None and found.id == asset.id
assert lib.asset_at(bank=1, index=6) is None
with pytest.raises(KeyError):
lib.register_bank_slot("unbekannt", 1, 1)
def test_missing_detection_and_relink(media_root: Path) -> None:
"""Fehlende Dateien werden markiert; Relink bindet neu (§13.3)."""
lib = MediaLibrary(media_root)
asset = lib.import_file(media_root / "clips" / "loop.mp4").asset
(media_root / "clips" / "loop.mp4").unlink() # Datei verschwindet
assert lib.mark_missing() == [asset.id]
assert asset.id in lib.missing_asset_ids
# Relink auf eine neue Datei mit gleichem Namen
(media_root / "clips" / "loop.mp4").write_bytes(b"neuer-inhalt")
updated = lib.relink(asset.id, media_root / "clips" / "loop.mp4")
assert updated.content_hash != asset.content_hash # neuer Inhalt → neuer Hash
assert asset.id not in lib.missing_asset_ids
def test_relink_rejects_missing_target(media_root: Path) -> None:
lib = MediaLibrary(media_root)
asset = lib.import_file(media_root / "clips" / "loop.mp4").asset
with pytest.raises(FileNotFoundError):
lib.relink(asset.id, media_root / "nirgendwo.mp4")
def test_library_all_and_get(media_root: Path) -> None:
lib = MediaLibrary(media_root)
a = lib.import_file(media_root / "clips" / "intro.mp4").asset
b = lib.import_file(media_root / "clips" / "loop.mp4").asset
assert {x.id for x in lib.all()} == {a.id, b.id}
assert lib.get(a.id).rel_path == "clips/intro.mp4"
assert lib.get("gibts-nicht") is None