"""Unit tests for media file utilities. Tests media file checking, path resolution, video file counting, and media file existence validation. """ from pathlib import Path import pytest from src.server.utils.media import ( FANART_FILENAME, LOGO_FILENAME, NFO_FILENAME, POSTER_FILENAME, VIDEO_EXTENSIONS, check_media_files, count_video_files, get_media_file_paths, has_all_images, has_video_files, ) class TestCheckMediaFiles: """Tests for check_media_files function.""" def test_all_files_exist(self, tmp_path: Path): """Returns True for all file types when they exist.""" (tmp_path / POSTER_FILENAME).touch() (tmp_path / LOGO_FILENAME).touch() (tmp_path / FANART_FILENAME).touch() (tmp_path / NFO_FILENAME).touch() result = check_media_files(tmp_path) assert result == { "poster": True, "logo": True, "fanart": True, "nfo": True, } def test_no_files_exist(self, tmp_path: Path): """Returns False for all when no media files exist.""" result = check_media_files(tmp_path) assert all(v is False for v in result.values()) def test_partial_files(self, tmp_path: Path): """Returns mixed results when some files exist.""" (tmp_path / POSTER_FILENAME).touch() result = check_media_files(tmp_path) assert result["poster"] is True assert result["logo"] is False def test_accepts_string_path(self, tmp_path: Path): """Works with string path arguments.""" (tmp_path / POSTER_FILENAME).touch() result = check_media_files(str(tmp_path)) assert result["poster"] is True def test_selective_checks(self, tmp_path: Path): """Only checks requested file types.""" result = check_media_files( tmp_path, check_poster=True, check_logo=False, check_fanart=False, check_nfo=False, ) assert "poster" in result assert "logo" not in result assert "fanart" not in result assert "nfo" not in result class TestGetMediaFilePaths: """Tests for get_media_file_paths function.""" def test_returns_paths_for_existing_files(self, tmp_path: Path): """Returns Path objects for files that exist.""" (tmp_path / POSTER_FILENAME).touch() result = get_media_file_paths(tmp_path) assert result["poster"] == tmp_path / POSTER_FILENAME def test_returns_none_for_missing_files(self, tmp_path: Path): """Returns None for files that don't exist.""" result = get_media_file_paths(tmp_path) assert result["poster"] is None assert result["logo"] is None def test_selective_includes(self, tmp_path: Path): """Only includes requested file types.""" result = get_media_file_paths( tmp_path, include_poster=True, include_logo=False, include_fanart=False, include_nfo=False, ) assert "poster" in result assert "logo" not in result def test_accepts_string_path(self, tmp_path: Path): """Works with string path arguments.""" (tmp_path / NFO_FILENAME).touch() result = get_media_file_paths(str(tmp_path)) assert result["nfo"] is not None class TestHasAllImages: """Tests for has_all_images function.""" def test_true_when_all_images_present(self, tmp_path: Path): """Returns True when poster, logo, and fanart all exist.""" (tmp_path / POSTER_FILENAME).touch() (tmp_path / LOGO_FILENAME).touch() (tmp_path / FANART_FILENAME).touch() assert has_all_images(tmp_path) is True def test_false_when_missing_one(self, tmp_path: Path): """Returns False when any image is missing.""" (tmp_path / POSTER_FILENAME).touch() (tmp_path / LOGO_FILENAME).touch() # fanart missing assert has_all_images(tmp_path) is False def test_false_when_no_images(self, tmp_path: Path): """Returns False when no images exist.""" assert has_all_images(tmp_path) is False def test_nfo_not_required(self, tmp_path: Path): """NFO file presence doesn't affect image check.""" (tmp_path / POSTER_FILENAME).touch() (tmp_path / LOGO_FILENAME).touch() (tmp_path / FANART_FILENAME).touch() # NFO is not an image - shouldn't matter assert has_all_images(tmp_path) is True def test_accepts_string_path(self, tmp_path: Path): """Works with string path arguments.""" assert has_all_images(str(tmp_path)) is False class TestCountVideoFiles: """Tests for count_video_files function.""" def test_counts_mp4_files(self, tmp_path: Path): """Counts .mp4 video files.""" (tmp_path / "ep01.mp4").touch() (tmp_path / "ep02.mp4").touch() assert count_video_files(tmp_path) == 2 def test_counts_multiple_extensions(self, tmp_path: Path): """Counts video files with various extensions.""" (tmp_path / "ep01.mp4").touch() (tmp_path / "ep02.mkv").touch() (tmp_path / "ep03.avi").touch() assert count_video_files(tmp_path) == 3 def test_ignores_non_video_files(self, tmp_path: Path): """Non-video files are not counted.""" (tmp_path / "ep01.mp4").touch() (tmp_path / "poster.jpg").touch() (tmp_path / "readme.txt").touch() assert count_video_files(tmp_path) == 1 def test_recursive_counts_subdirectories(self, tmp_path: Path): """Recursive mode counts videos in subdirectories.""" s1 = tmp_path / "Season 1" s1.mkdir() (s1 / "ep01.mp4").touch() (s1 / "ep02.mp4").touch() s2 = tmp_path / "Season 2" s2.mkdir() (s2 / "ep01.mkv").touch() assert count_video_files(tmp_path, recursive=True) == 3 def test_non_recursive_skips_subdirectories(self, tmp_path: Path): """Non-recursive mode only counts root-level files.""" (tmp_path / "ep01.mp4").touch() sub = tmp_path / "Season 1" sub.mkdir() (sub / "ep02.mp4").touch() assert count_video_files(tmp_path, recursive=False) == 1 def test_nonexistent_folder_returns_zero(self): """Returns 0 for nonexistent folder.""" assert count_video_files("/nonexistent/path") == 0 def test_empty_folder_returns_zero(self, tmp_path: Path): """Returns 0 for empty folder.""" assert count_video_files(tmp_path) == 0 def test_case_insensitive_extensions(self, tmp_path: Path): """Video extensions are matched case-insensitively.""" (tmp_path / "ep01.MP4").touch() (tmp_path / "ep02.Mkv").touch() assert count_video_files(tmp_path) == 2 class TestHasVideoFiles: """Tests for has_video_files function.""" def test_true_when_videos_exist(self, tmp_path: Path): """Returns True when video files are present.""" (tmp_path / "ep01.mp4").touch() assert has_video_files(tmp_path) is True def test_false_when_no_videos(self, tmp_path: Path): """Returns False when no video files exist.""" (tmp_path / "readme.txt").touch() assert has_video_files(tmp_path) is False def test_false_for_nonexistent_folder(self): """Returns False for nonexistent folder.""" assert has_video_files("/nonexistent/path") is False def test_finds_videos_in_subdirectories(self, tmp_path: Path): """Detects videos in nested subdirectories.""" sub = tmp_path / "Season 1" sub.mkdir() (sub / "ep01.mkv").touch() assert has_video_files(tmp_path) is True def test_all_supported_extensions(self, tmp_path: Path): """All supported video extensions are recognized.""" for ext in VIDEO_EXTENSIONS: (tmp_path / f"test{ext}").touch() assert has_video_files(tmp_path) is True class TestConstants: """Tests for module constants.""" def test_standard_filenames(self): """Standard media filenames match Kodi/Plex conventions.""" assert POSTER_FILENAME == "poster.jpg" assert LOGO_FILENAME == "logo.png" assert FANART_FILENAME == "fanart.jpg" assert NFO_FILENAME == "tvshow.nfo" def test_video_extensions_includes_common_formats(self): """VIDEO_EXTENSIONS includes all common video formats.""" for ext in [".mp4", ".mkv", ".avi", ".webm"]: assert ext in VIDEO_EXTENSIONS