From 1efe9a82c1274deefc9647cb885d768340843df5 Mon Sep 17 00:00:00 2001 From: Francwa Date: Tue, 26 May 2026 00:45:14 +0200 Subject: [PATCH] feat(dot_alfred): load_by_tmdb_id on release repos MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Series repo returns (release, folder) so the upcoming sync orchestrator can feed the library index's upsert(..., path=...). Movie repo returns the release alone (folder is on release.folder by the one-folder-one-file convention) — kept as a semantic alias of find_by_tmdb_id for symmetry with the series side. --- .../persistence/dot_alfred/v2/repository.py | 27 +++++++++++++ .../dot_alfred/v2/test_release_repository.py | 38 +++++++++++++++++++ 2 files changed, 65 insertions(+) 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 ):