refactor(tmdb): ACL pass — push VOs into DTOs, split search per media type

Anti-corruption boundary tightened on the TMDB adapter:

* TmdbMovieInfo / TmdbShowInfo now carry domain VOs (TmdbId, ImdbId,
  MovieTitle, ReleaseYear, ShowStatus) instead of raw scalars —
  validation happens at the boundary, not three layers later.
* ShowStatus enum added (domain/tv_shows/value_objects) with a
  from_tmdb() mapper that falls back to UNKNOWN + logs a warning on
  unrecognized values. TVShow.status is now ShowStatus, not str.
* MovieTitle cap raised from 100 to 150 chars.
* MediaResult / ExternalIds dropped. Replaced by per-media search
  DTOs: TmdbMovieSearchResult and TmdbShowSearchResult. Neither
  carries imdb_id — search no longer enriches with external_ids
  (callers needing imdb_id follow up with get_movie_info /
  get_tv_show_info on the chosen tmdb_id).
* TMDBClient: search_multi / search_media / _parse_result removed.
  search_movies (/search/movie) and search_shows (/search/tv) added,
  each parsing hits into VO-typed DTOs.
* SearchMovieUseCase returns a list of MovieHit (flattened to
  primitives for the agent). New symmetric SearchShowUseCase +
  ShowHit / SearchShowResponse DTOs.
* agent/tools/api.py: find_media_imdb_id → search_movies +
  search_shows wrappers.
* FileEntry moved from domain/shared/ports/filesystem_scanner.py to
  domain/shared/file_entry.py (it's a DTO, not a Protocol); size_kb
  (float) → size (int bytes). Scanner and SubtitleIdentifier
  updated.

Tests: 79/79 pass on tests/infrastructure/api/ +
tests/application/test_search_movie.py +
tests/application/test_search_show.py.
This commit is contained in:
2026-05-26 05:45:30 +02:00
parent cffafa2e60
commit c62ae81275
29 changed files with 735 additions and 490 deletions
+11 -8
View File
@@ -12,6 +12,9 @@ from datetime import date
import pytest
from alfred.domain.movies.value_objects import MovieTitle, ReleaseYear
from alfred.domain.shared.value_objects import ImdbId, TmdbId
from alfred.domain.tv_shows.value_objects import ShowStatus
from alfred.infrastructure.api.tmdb.dto import (
TmdbMovieInfo,
TmdbSeasonInfo,
@@ -42,10 +45,10 @@ class TestParseTvShowInfoHappyPath:
today=REF_DATE,
)
assert info == TmdbShowInfo(
tmdb_id=84958,
imdb_id="tt0804484",
tmdb_id=TmdbId(84958),
imdb_id=ImdbId("tt0804484"),
name="Foundation",
status="Returning Series",
status=ShowStatus.RETURNING_SERIES,
seasons=(),
)
@@ -192,10 +195,10 @@ class TestParseMovieInfoHappyPath:
{"imdb_id": "tt1375666"},
)
assert info == TmdbMovieInfo(
tmdb_id=27205,
imdb_id="tt1375666",
title="Inception",
release_year=2010,
tmdb_id=TmdbId(27205),
imdb_id=ImdbId("tt1375666"),
title=MovieTitle("Inception"),
release_year=ReleaseYear(2010),
)
def test_release_year_extracted_from_release_date(self):
@@ -203,7 +206,7 @@ class TestParseMovieInfoHappyPath:
_movie_details(release_date="1999-03-31"),
{},
)
assert info.release_year == 1999
assert info.release_year == ReleaseYear(1999)
class TestParseMovieInfoImdb: