"""Tests for the movie object and the pool layout it derives (src.core.movie).""" from pathlib import Path import pytest from src.core.movie import ( DEFAULT_VERSION_LABEL, MediaFile, Movie, identity_key, media_filename, normalize_3d_label, ) @pytest.mark.parametrize( "label,expected", [ ("HSBS", "HSBS"), ("hsbs", "HSBS"), ("3D HSBS", "HSBS"), ("3d.hsbs", "HSBS"), ("3D_FTAB", "FTAB"), ("3d-mvc", "MVC"), ("4K", None), ("Director's Cut", None), ("", None), (None, None), ], ) def test_normalize_3d_label(label, expected): assert normalize_3d_label(label) == expected class TestMediaFilename: def test_no_label_is_the_bare_stem(self): assert ( media_filename("Matrix (1999)", "", ".mkv", grouped=False) == "Matrix (1999).mkv" ) def test_3d_label_uses_jellyfins_documented_form(self): assert ( media_filename("Avatar (2009)", "3D HSBS", ".mkv", grouped=False) == "Avatar (2009).3D.HSBS.mkv" ) # same spelling grouped — "." is a valid separator after the folder name assert ( media_filename("Avatar (2009)", "hsbs", ".mkv", grouped=True) == "Avatar (2009).3D.HSBS.mkv" ) def test_plain_label_uses_dash_form(self): assert ( media_filename("Avatar (2009)", "4K", ".mkv", grouped=True) == "Avatar (2009) - 4K.mkv" ) def test_grouped_empty_label_gets_a_default(self): assert ( media_filename("Avatar (2009)", "", ".mkv", grouped=True) == f"Avatar (2009) - {DEFAULT_VERSION_LABEL}.mkv" ) class TestIdentityKey: def test_tmdb_id_wins(self): assert identity_key(603, "Matrix", "1999") == "tmdb-603" assert identity_key("603", "cokoli", None) == "tmdb-603" def test_slug_fallback_without_id(self): assert identity_key(None, "Matrix", "1999") == "name-matrix-1999" def test_slug_folds_diacritics(self): assert identity_key(None, "Pelíšky", "1999") == "name-pelisky-1999" def test_slug_without_year(self): assert identity_key(None, "Nějaký film", None) == "name-nejaky-film" def _movie(**kwargs) -> Movie: base = { "key": "tmdb-19995", "title": "Avatar", "year": "2009", "tmdb_id": 19995, "library": "Filmy", } base.update(kwargs) return Movie(**base) class TestMovieShape: def test_stem_is_the_canonical_form(self): assert _movie().stem == "Avatar (2009) [tmdbid-19995]" def test_stem_falls_back_to_title_without_year(self): assert _movie(year=None, tmdb_id=None).stem == "Avatar" def test_single_video_is_not_grouped(self): m = _movie(media=[MediaFile(Path("/pool/Filmy/x.mkv"))]) assert m.is_grouped is False def test_two_videos_are_grouped(self): m = _movie( media=[ MediaFile(Path("/a.mkv"), "3D HSBS"), MediaFile(Path("/b.mkv"), "Výchozí"), ] ) assert m.is_grouped is True def test_primary_prefers_the_plain_version(self): m = _movie( media=[MediaFile(Path("/a.mkv"), "3D HSBS"), MediaFile(Path("/b.mkv"), "")] ) assert m.primary.label == "" class TestDesiredLayout: def test_one_video_stays_flat_in_the_library(self, tmp_path): m = _movie(media=[MediaFile(tmp_path / "kdekoli.mkv", "")]) ((_media, target),) = m.desired_layout(tmp_path) assert target == tmp_path / "Filmy" / "Avatar (2009) [tmdbid-19995].mkv" def test_one_3d_video_keeps_the_3d_tag_flat(self, tmp_path): m = _movie(media=[MediaFile(tmp_path / "x.mkv", "3D HSBS")]) ((_m, target),) = m.desired_layout(tmp_path) assert target.name == "Avatar (2009) [tmdbid-19995].3D.HSBS.mkv" assert target.parent == tmp_path / "Filmy" def test_two_videos_move_into_the_movie_folder(self, tmp_path): m = _movie( media=[ MediaFile(tmp_path / "a.mkv", ""), MediaFile(tmp_path / "b.mkv", "3D HSBS"), ] ) targets = {mf.label: path for mf, path in m.desired_layout(tmp_path)} folder = tmp_path / "Filmy" / "Avatar (2009) [tmdbid-19995]" assert ( targets[""] == folder / f"Avatar (2009) [tmdbid-19995] - {DEFAULT_VERSION_LABEL}.mkv" ) assert targets["3D HSBS"] == folder / "Avatar (2009) [tmdbid-19995].3D.HSBS.mkv" def test_layout_follows_a_retitle(self, tmp_path): """Renaming the movie is just a different desired layout — the thing that used to be impossible for a grouped movie.""" m = _movie( title="Avatar: Nová verze", media=[ MediaFile(tmp_path / "a.mkv", "A"), MediaFile(tmp_path / "b.mkv", "B"), ], ) folders = {path.parent for _mf, path in m.desired_layout(tmp_path)} assert folders == { tmp_path / "Filmy" / "Avatar: Nová verze (2009) [tmdbid-19995]" } def test_layout_follows_a_library_move(self, tmp_path): m = _movie(library="Kids", media=[MediaFile(tmp_path / "a.mkv", "")]) ((_m, target),) = m.desired_layout(tmp_path) assert target.parent == tmp_path / "Kids" class TestRecordRoundTrip: def test_to_and_from_record(self, tmp_path): m = _movie( media=[MediaFile(tmp_path / "Filmy" / "a.mkv", "3D HSBS")], csfd_url="https://www.csfd.cz/film/1/", date="2026-01-01", attributes={"collection_sort": "01"}, meta={"year": "2009"}, ) record = m.to_record(tmp_path) assert record["media"] == [ {"role": "video", "label": "3D HSBS", "file": "Filmy/a.mkv"} ] back = Movie.from_record("tmdb-19995", record, tmp_path) assert back.title == "Avatar" assert back.year == "2009" assert back.tmdb_id == 19995 assert back.library == "Filmy" assert back.csfd_url == "https://www.csfd.cz/film/1/" assert back.attributes == {"collection_sort": "01"} assert back.meta == {"year": "2009"} assert back.media[0].path == tmp_path / "Filmy" / "a.mkv" assert back.media[0].format_3d == "HSBS" def test_from_record_builds_tags_via_tagmanager(self, tmp_path): from src.core.tag_manager import TagManager record = {"title": "Avatar", "media": [], "tags": ["Žánr/Sci-Fi"]} back = Movie.from_record("tmdb-19995", record, tmp_path, TagManager()) assert {t.full_path for t in back.tags} == {"Žánr/Sci-Fi"} @pytest.mark.parametrize( "label,expected", [ ("part1", 1), ("Part 2", 2), ("pt3", 3), ("CD1", 1), ("disc-2", 2), ("dvd_4", 4), ("disk12", 12), ("4K", None), ("3D HSBS", None), ("", None), ("partition", None), ], ) def test_normalize_part_label(label, expected): from src.core.movie import normalize_part_label assert normalize_part_label(label) == expected