diff --git a/alfred/infrastructure/persistence/dot_alfred/v2/repository.py b/alfred/infrastructure/persistence/dot_alfred/v2/repository.py index 9cf518f..fe02642 100644 --- a/alfred/infrastructure/persistence/dot_alfred/v2/repository.py +++ b/alfred/infrastructure/persistence/dot_alfred/v2/repository.py @@ -117,6 +117,21 @@ class DotAlfredSeriesReleaseRepository: return release return None + def load_by_tmdb_id( + self, tmdb_id: TmdbId + ) -> tuple[SeriesRelease, str] | None: + """Return ``(release, show_folder_name)`` for ``tmdb_id``, or ``None``. + + Same lookup as :meth:`find_by_tmdb_id` but also returns the + folder name the release lives in — needed by the upcoming + sync orchestrator to feed the library index's ``upsert(..., + path=...)``. + """ + for folder, release in self._iter_library(): + if release.tmdb_id == tmdb_id: + return release, folder + return None + def find_all(self) -> list[SeriesRelease]: """Return every readable release under ``library_root/``. @@ -198,6 +213,18 @@ class DotAlfredMovieReleaseRepository: return release return None + def load_by_tmdb_id(self, tmdb_id: TmdbId) -> MovieRelease | None: + """Return the :class:`MovieRelease` for ``tmdb_id``, or ``None``. + + Movies carry their folder on ``release.folder`` (one-folder- + one-file convention), so no separate folder name is returned — + this is just a semantic alias of :meth:`find_by_tmdb_id` + provided for symmetry with + :meth:`DotAlfredSeriesReleaseRepository.load_by_tmdb_id`, the + sync orchestrators rely on the same name on both sides. + """ + return self.find_by_tmdb_id(tmdb_id) + def find_all(self) -> list[MovieRelease]: return [release for _folder, release in self._iter_library()] diff --git a/tests/infrastructure/persistence/dot_alfred/v2/test_release_repository.py b/tests/infrastructure/persistence/dot_alfred/v2/test_release_repository.py index 79ab606..3521582 100644 --- a/tests/infrastructure/persistence/dot_alfred/v2/test_release_repository.py +++ b/tests/infrastructure/persistence/dot_alfred/v2/test_release_repository.py @@ -90,6 +90,30 @@ class TestSeriesReleaseRepositoryReads: assert len(results) == 1 +class TestSeriesReleaseRepositoryLoadByTmdbId: + def test_returns_release_and_folder_tuple( + self, tv_library, foundation_release + ): + repo = DotAlfredSeriesReleaseRepository(tv_library) + repo.save(foundation_release, show_folder="Foundation") + result = repo.load_by_tmdb_id(TmdbId(84958)) + assert result is not None + release, folder = result + assert release == foundation_release + assert folder == "Foundation" + + def test_returns_none_when_tmdb_id_absent( + self, tv_library, foundation_release + ): + repo = DotAlfredSeriesReleaseRepository(tv_library) + repo.save(foundation_release, show_folder="Foundation") + assert repo.load_by_tmdb_id(TmdbId(999999)) is None + + def test_returns_none_on_empty_library(self, tv_library): + repo = DotAlfredSeriesReleaseRepository(tv_library) + assert repo.load_by_tmdb_id(TmdbId(84958)) is None + + class TestSeriesReleaseRepositoryDelete: def test_delete_removes_sidecar(self, tv_library, foundation_release): repo = DotAlfredSeriesReleaseRepository(tv_library) @@ -123,6 +147,20 @@ class TestMovieReleaseRepository: restored = repo.find_by_tmdb_id(TmdbId(27205)) assert restored == inception_release + def test_load_by_tmdb_id_returns_release( + self, movie_library, inception_release + ): + repo = DotAlfredMovieReleaseRepository(movie_library) + repo.save(inception_release) + assert repo.load_by_tmdb_id(TmdbId(27205)) == inception_release + + def test_load_by_tmdb_id_returns_none_when_absent( + self, movie_library, inception_release + ): + repo = DotAlfredMovieReleaseRepository(movie_library) + repo.save(inception_release) + assert repo.load_by_tmdb_id(TmdbId(999999)) is None + def test_anchor_mismatch_logs_warning( self, movie_library, inception_release, caplog ):