"""Tests for indexer/enrich.py — season/title corroboration and the end-to-end pool.""" import asyncio import shutil import subprocess from pathlib import Path import pytest from meshbay_common.protocol import IndexEntry from meshbay_node.indexer.enrich import Enricher, _season_from_ancestors, _title_from_siblings from meshbay_node.media_cache import MediaCache _HAVE_FFMPEG = shutil.which("ffmpeg") and shutil.which("ffprobe") # ── pure helpers, no ffmpeg needed ─────────────────────────────────────────── def test_season_from_ancestors_finds_season_folder(tmp_path): folder = tmp_path / "Some Show" / "Season 2" folder.mkdir(parents=True) ep = folder / "01 - Episode Title.mkv" ep.touch() assert _season_from_ancestors(ep) == 2 def test_season_from_ancestors_none_when_no_season_folder(tmp_path): folder = tmp_path / "Movies" folder.mkdir() f = folder / "Some Movie 2015.mkv" f.touch() assert _season_from_ancestors(f) is None def test_title_from_siblings_borrows_from_a_titled_sibling(tmp_path): folder = tmp_path / "Acronym Show" folder.mkdir() titled = folder / "Some.Show.Name.S01E01.720p.mkv" untitled = folder / "S01E02.SUBFRENCH.720p.mkv" titled.touch() untitled.touch() assert _title_from_siblings(untitled) == "Some Show Name" def test_title_from_siblings_none_when_no_titled_sibling(tmp_path): folder = tmp_path / "Acronym Show" folder.mkdir() (folder / "S01E02.mkv").touch() assert _title_from_siblings(folder / "S01E02.mkv") is None # ── end-to-end against a real (tiny, synthetic) video file ────────────────── pytestmark_ffmpeg = pytest.mark.skipif(not _HAVE_FFMPEG, reason="ffmpeg/ffprobe not installed") def _make_clip(path: Path) -> None: subprocess.run( ["ffmpeg", "-hide_banner", "-loglevel", "error", "-y", "-f", "lavfi", "-i", "testsrc=size=320x240:rate=25:duration=1", "-c:v", "libx264", "-preset", "ultrafast", "-an", str(path)], check=True, capture_output=True, ) @pytest.fixture async def media_cache(tmp_path): c = MediaCache(db_path=tmp_path / "media_cache.db") await c.open() yield c await c.close() @pytestmark_ffmpeg @pytest.mark.asyncio async def test_enricher_populates_fields_and_stores_thumbnail(tmp_path, media_cache): clip = tmp_path / "Some.Movie.2015.1080p.mkv" _make_clip(clip) entry = IndexEntry(id="fileid1", name=clip.name, path=clip.name, size=clip.stat().st_size, type="video", added_at=0) enricher = Enricher(media_cache) done = asyncio.get_event_loop().create_future() async def on_done(file_id, fields): done.set_result((file_id, fields)) enricher.spawn(entry, clip, on_done) file_id, fields = await asyncio.wait_for(done, timeout=30) assert file_id == "fileid1" assert fields["width"] == 320 assert fields["height"] == 240 assert fields["display_title"] == "Some Movie" assert fields.get("thumb_hash") stored = await media_cache.get_thumb(fields["thumb_hash"]) assert stored is not None and len(stored) > 0 @pytestmark_ffmpeg @pytest.mark.asyncio async def test_enricher_handles_episode_with_season_from_folder(tmp_path, media_cache): # Filename carries only a bare episode number, no SxxExx token — guessit # confirmed (separately) not to find a season here at all — so the # season must come from the ancestor folder (§3.4's non-standard case). folder = tmp_path / "Some Show" / "Saison 3" folder.mkdir(parents=True) titled_sibling = folder / "Some.Show.Episode.06.mkv" titled_sibling.touch() clip = folder / "Episode.07.720p.mkv" _make_clip(clip) entry = IndexEntry(id="fileid2", name=clip.name, path=str(clip.relative_to(tmp_path)), size=clip.stat().st_size, type="video", added_at=0) enricher = Enricher(media_cache) done = asyncio.get_event_loop().create_future() async def on_done(file_id, fields): done.set_result((file_id, fields)) enricher.spawn(entry, clip, on_done) file_id, fields = await asyncio.wait_for(done, timeout=30) assert fields["display_title"] == "Some Show" assert fields["season"] == 3 assert fields["episode"] == 7