362e089be0
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).
90 lines
3.5 KiB
Python
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
|