from __future__ import annotations from pathlib import Path from typing import Any import pytest from ruamel.yaml import YAML #: Half a second of silence, so ``mutagen`` reports a real duration and tags can be #: written onto a file that is actually an MP3. Generated once with ffmpeg. SILENCE = Path(__file__).parent / "data" / "silence.mp3" VALID_CONFIG: dict[str, Any] = { "general": { "library": {"root": "music", "cache": ".cache"}, "serial_port": "/dev/ttyUSB0", "alsa_device": "simulate", "min_volume": 0, "max_volume": 60, "initial_volume": 40, }, "figures": { "fuchs": {"id": "04a1b2c3d4", "colors": ["#ff6600", "#ffcc00", "#331100", "wff"]}, # One of each, so the figure-kind branch is exercised by every fixture. "eule": { "id": "04b2c3d4e5", "colors": ["#3355ff", "#66aaff", "#001133", "#ffffff"], "kind": "book", }, }, } def write_track(path: Path, **tags: str) -> Path: """Copy the silence fixture to ``path`` and stamp the given easy-mode ID3 tags.""" import mutagen path.parent.mkdir(parents=True, exist_ok=True) path.write_bytes(SILENCE.read_bytes()) if tags: audio = mutagen.File(path, easy=True) if audio.tags is None: audio.add_tags() for key, value in tags.items(): audio[key] = value audio.save() return path @pytest.fixture def config_dir(tmp_path: Path) -> Path: """A directory holding a small but structurally real music library. Two figure folders (matching ``VALID_CONFIG``), one music album, one audiobook and one podcast - enough for every branch the scanner has. """ root = tmp_path / "music" for figure, tracks in (("fuchs", 3), ("eule", 2)): for index in range(tracks): write_track( root / "Figuren" / figure / f"{index:02d} - track.mp3", title=f"Track {index}", album="Waldlieder", albumartist="Die Tiere", ) for index in range(2): write_track( root / "Musik" / "Kinderparty - Kinderparty Lieder" / f"{index:02d} - lied.mp3", title=f"Lied {index}", album="Kinderparty Lieder", albumartist="Kinderparty", ) for index in range(2): write_track( root / "Hörbücher" / "Conni - Conni in den Bergen" / f"{index:02d} - teil.mp3", title=f"Teil {index}", album="Conni in den Bergen", albumartist="Conni, Julia Boehme", ) podcast = root / "Kinderpodcasts" / "Wissen macht Ah" for date, title in (("20240101", "Alt"), ("20260101", "Neu")): write_track( podcast / f"{date} - {title}.mp3", title=title, album="Wissen macht Ah! - Podcast", albumartist="Ein Name, Noch Einer, Und Einer", ) # The two kinds of scratch file a podcast downloader leaves behind. (podcast / "archive.json").write_text("[]") (podcast / ".podcast-dl-abc.download.tmp").write_bytes(b"") return tmp_path def write_config(directory: Path, data: dict[str, Any], name: str = "config.yml") -> Path: path = directory / name with path.open("w", encoding="utf-8") as handle: YAML(typ="safe").dump(data, handle) return path