"""Tests for the releases domain builders.""" import pytest from alfred.domain.releases.builders import ( SeasonReleaseBuilder, SeriesReleaseBuilder, ) from alfred.domain.releases.entities import EpisodeRelease, TrackProfile from alfred.domain.releases.value_objects import EpisodeRange, ReleaseMode from alfred.domain.shared.exceptions import ValidationError from alfred.domain.shared.value_objects import FilePath, ImdbId, TmdbId from alfred.domain.tv_shows.value_objects import EpisodeNumber, SeasonNumber def _ep(start: int, end: int | None = None, *, file: str | None = None) -> EpisodeRelease: end_n = EpisodeNumber(end if end is not None else start) label = file or f"S01E{start:02d}.mkv" return EpisodeRelease( episodes=EpisodeRange(EpisodeNumber(start), end_n), file_path=FilePath(label), tracks=TrackProfile(), ) # --------------------------------------------------------------------------- # SeasonReleaseBuilder # --------------------------------------------------------------------------- class TestSeasonReleaseBuilder: def test_build_empty(self): b = SeasonReleaseBuilder(1, folder="X", mode=ReleaseMode.PACK) s = b.build() assert s.season_number == SeasonNumber(1) assert s.episodes == () assert s.mode == ReleaseMode.PACK def test_int_normalized_to_vo(self): b = SeasonReleaseBuilder(3, folder="X", mode=ReleaseMode.EPISODIC) assert b.season_number == SeasonNumber(3) def test_add_episode_returns_self(self): b = SeasonReleaseBuilder(1, folder="X", mode=ReleaseMode.PACK) assert b.add_episode(_ep(1)) is b def test_episodes_sorted_by_start(self): b = SeasonReleaseBuilder(1, folder="X", mode=ReleaseMode.PACK) b.add_episode(_ep(3)).add_episode(_ep(1)).add_episode(_ep(2)) s = b.build() starts = [ep.episodes.start.value for ep in s.episodes] assert starts == [1, 2, 3] def test_overlap_raises(self): # E01-E03 + E02 -> E02 overlaps with the first range b = SeasonReleaseBuilder(1, folder="X", mode=ReleaseMode.PACK) b.add_episode(_ep(1, 3)).add_episode(_ep(2)) with pytest.raises(ValidationError): b.build() def test_adjacent_ranges_dont_overlap(self): # E01 + E02-E04 + E05 — all touch but no overlap b = SeasonReleaseBuilder(1, folder="X", mode=ReleaseMode.PACK) b.add_episode(_ep(1)).add_episode(_ep(2, 4)).add_episode(_ep(5)) s = b.build() assert s.episode_count() == 5 def test_set_folder(self): b = SeasonReleaseBuilder(1, folder="X", mode=ReleaseMode.PACK) b.set_folder("Y") assert b.build().folder == "Y" def test_set_mode(self): b = SeasonReleaseBuilder(1, folder="X", mode=ReleaseMode.PACK) b.set_mode(ReleaseMode.EPISODIC) assert b.build().mode == ReleaseMode.EPISODIC def test_from_existing_round_trip(self): b = SeasonReleaseBuilder(1, folder="Show.S01", mode=ReleaseMode.PACK) b.add_episode(_ep(1)).add_episode(_ep(2)) original = b.build() b2 = SeasonReleaseBuilder.from_existing(original) rebuilt = b2.build() assert rebuilt == original def test_from_existing_then_mutate(self): b = SeasonReleaseBuilder(1, folder="X", mode=ReleaseMode.PACK) b.add_episode(_ep(1)) original = b.build() b2 = SeasonReleaseBuilder.from_existing(original).add_episode(_ep(2)) rebuilt = b2.build() assert len(rebuilt.episodes) == 2 # --------------------------------------------------------------------------- # SeriesReleaseBuilder # --------------------------------------------------------------------------- class TestSeriesReleaseBuilder: def test_build_empty(self): b = SeriesReleaseBuilder(tmdb_id=TmdbId(84958)) r = b.build() assert r.tmdb_id == TmdbId(84958) assert r.imdb_id is None assert r.seasons == () def test_int_and_str_normalized(self): b = SeriesReleaseBuilder(tmdb_id=84958, imdb_id="tt0804484") r = b.build() assert r.tmdb_id == TmdbId(84958) assert r.imdb_id == ImdbId("tt0804484") def test_season_builder_create_requires_folder_and_mode(self): b = SeriesReleaseBuilder(tmdb_id=TmdbId(1)) with pytest.raises(ValidationError): b.season_builder(1) with pytest.raises(ValidationError): b.season_builder(1, folder="X") with pytest.raises(ValidationError): b.season_builder(1, mode=ReleaseMode.PACK) def test_season_builder_reuses_existing(self): b = SeriesReleaseBuilder(tmdb_id=TmdbId(1)) sb1 = b.season_builder(1, folder="X", mode=ReleaseMode.PACK) sb2 = b.season_builder(1) # no folder/mode — reuse assert sb1 is sb2 def test_season_builder_can_override_folder_and_mode(self): b = SeriesReleaseBuilder(tmdb_id=TmdbId(1)) b.season_builder(1, folder="X", mode=ReleaseMode.PACK) b.season_builder(1, folder="Y", mode=ReleaseMode.EPISODIC) season = b.build().seasons[0] assert season.folder == "Y" assert season.mode == ReleaseMode.EPISODIC def test_seasons_sorted_on_build(self): b = SeriesReleaseBuilder(tmdb_id=TmdbId(1)) b.season_builder(2, folder="S02", mode=ReleaseMode.PACK) b.season_builder(1, folder="S01", mode=ReleaseMode.PACK) r = b.build() nums = [s.season_number.value for s in r.seasons] assert nums == [1, 2] def test_add_season_replaces(self): b = SeriesReleaseBuilder(tmdb_id=TmdbId(1)) b.season_builder(1, folder="OLD", mode=ReleaseMode.PACK) # Build a SeasonRelease via its own builder and attach via add_season: replacement = SeasonReleaseBuilder( 1, folder="NEW", mode=ReleaseMode.EPISODIC ).build() b.add_season(replacement) season = b.build().seasons[0] assert season.folder == "NEW" assert season.mode == ReleaseMode.EPISODIC def test_set_imdb_id_string_normalized(self): b = SeriesReleaseBuilder(tmdb_id=TmdbId(1)) b.set_imdb_id("tt0804484") assert b.build().imdb_id == ImdbId("tt0804484") def test_set_imdb_id_none(self): b = SeriesReleaseBuilder(tmdb_id=TmdbId(1), imdb_id="tt0804484") b.set_imdb_id(None) assert b.build().imdb_id is None def test_from_existing_round_trip(self): b = SeriesReleaseBuilder(tmdb_id=TmdbId(84958), imdb_id="tt0804484") sb = b.season_builder(1, folder="S01", mode=ReleaseMode.PACK) sb.add_episode(_ep(1)).add_episode(_ep(2)) original = b.build() b2 = SeriesReleaseBuilder.from_existing(original) rebuilt = b2.build() assert rebuilt == original def test_from_existing_then_mutate(self): b = SeriesReleaseBuilder(tmdb_id=TmdbId(1)) sb = b.season_builder(1, folder="S01", mode=ReleaseMode.PACK) sb.add_episode(_ep(1)) original = b.build() b2 = SeriesReleaseBuilder.from_existing(original) b2.season_builder(2, folder="S02", mode=ReleaseMode.PACK).add_episode(_ep(1)) rebuilt = b2.build() assert len(rebuilt.seasons) == 2