"""Tests for the show-tree walker.""" from __future__ import annotations from alfred.application.library.walker import walk_show from alfred.infrastructure.filesystem.scanner import PathlibFilesystemScanner from alfred.infrastructure.knowledge.release_kb import YamlReleaseKnowledge _KB = YamlReleaseKnowledge() _SCANNER = PathlibFilesystemScanner() def _episode(dir_path, name: str) -> None: (dir_path / name).write_bytes(b"") class TestSeasonFolderDetection: def test_keeps_folders_with_season_token(self, tmp_path): show = tmp_path / "Foundation" show.mkdir() (show / "Foundation.S01.1080p.WEB-DL.x265-GROUP").mkdir() (show / "Foundation.S02.Complete.1080p.WEB-DL-GROUP").mkdir() tree = walk_show(show, scanner=_SCANNER, kb=_KB) names = sorted(s.season_dir.name for s in tree.season_folders) assert names == [ "Foundation.S01.1080p.WEB-DL.x265-GROUP", "Foundation.S02.Complete.1080p.WEB-DL-GROUP", ] def test_skips_folders_without_season_token(self, tmp_path): show = tmp_path / "Foundation" show.mkdir() (show / "Sample").mkdir() (show / "Soundtrack").mkdir() (show / "Foundation.S01.WEB").mkdir() tree = walk_show(show, scanner=_SCANNER, kb=_KB) assert [s.season_dir.name for s in tree.season_folders] == [ "Foundation.S01.WEB" ] def test_season_token_is_case_insensitive(self, tmp_path): show = tmp_path / "Foundation" show.mkdir() (show / "Foundation.s01.WEB").mkdir() tree = walk_show(show, scanner=_SCANNER, kb=_KB) assert len(tree.season_folders) == 1 def test_season_token_rejects_in_word(self, tmp_path): # "Season01" without separator must not match (we want a real # Sxx token, not any letter S followed by digits). show = tmp_path / "WeirdShow" show.mkdir() (show / "Pass100").mkdir() (show / "ABS01XYZ").mkdir() tree = walk_show(show, scanner=_SCANNER, kb=_KB) assert tree.season_folders == () def test_ignores_files_at_show_root(self, tmp_path): show = tmp_path / "Foundation" show.mkdir() (show / "Foundation.S01.mkv").write_bytes(b"") # file, not a folder (show / "Foundation.S01.WEB").mkdir() tree = walk_show(show, scanner=_SCANNER, kb=_KB) assert [s.season_dir.name for s in tree.season_folders] == [ "Foundation.S01.WEB" ] class TestVideoFileCollection: def test_keeps_only_video_extensions(self, tmp_path): show = tmp_path / "Foundation" show.mkdir() season = show / "Foundation.S01.WEB" season.mkdir() _episode(season, "Foundation.S01E01.mkv") _episode(season, "Foundation.S01E02.mp4") _episode(season, "Foundation.S01E01.eng.srt") # not a video _episode(season, "info.nfo") # not a video tree = walk_show(show, scanner=_SCANNER, kb=_KB) (folder,) = tree.season_folders suffixes = sorted(p.suffix for p in folder.video_files) assert suffixes == [".mkv", ".mp4"] def test_empty_season_folder_surfaces(self, tmp_path): show = tmp_path / "Foundation" show.mkdir() (show / "Foundation.S01.WEB").mkdir() tree = walk_show(show, scanner=_SCANNER, kb=_KB) (folder,) = tree.season_folders assert folder.video_files == () def test_single_pack_video_is_kept(self, tmp_path): show = tmp_path / "Foundation" show.mkdir() season = show / "Foundation.S01.Complete.WEB" season.mkdir() _episode(season, "Foundation.S01.Complete.WEB.mkv") tree = walk_show(show, scanner=_SCANNER, kb=_KB) (folder,) = tree.season_folders assert [p.name for p in folder.video_files] == [ "Foundation.S01.Complete.WEB.mkv" ] def test_does_not_recurse_into_sub_subfolders(self, tmp_path): show = tmp_path / "Foundation" show.mkdir() season = show / "Foundation.S01.WEB" season.mkdir() nested = season / "Extras" nested.mkdir() _episode(nested, "Foundation.S01E01.mkv") tree = walk_show(show, scanner=_SCANNER, kb=_KB) (folder,) = tree.season_folders assert folder.video_files == () class TestEdgeCases: def test_empty_show_root(self, tmp_path): show = tmp_path / "Empty" show.mkdir() tree = walk_show(show, scanner=_SCANNER, kb=_KB) assert tree.show_root == show assert tree.season_folders == () def test_missing_show_root_returns_empty_tree(self, tmp_path): # Scanner returns [] for non-existent paths; walker must not raise. tree = walk_show(tmp_path / "Nope", scanner=_SCANNER, kb=_KB) assert tree.season_folders == ()