feat(tmdb): add TmdbMovieInfo DTO and get_movie_info
Symmetric to TmdbShowInfo / get_tv_show_info — gives the upcoming
sync_movie orchestrator a typed cache snapshot for the v2 movie
library index.
* TmdbMovieInfo(tmdb_id, imdb_id, title, release_year)
* parse_movie_info(details, external_ids) — pure builder, parses
release_year from the first 4 chars of release_date (None on
missing/empty/non-numeric)
* TMDBClient.get_movie_info(tmdb_id) — aggregates
/movie/{id} + /movie/{id}/external_ids and feeds the parser
Tests cover happy path, missing/null/empty imdb_id, every
release_year edge (none/empty/short/non-numeric/missing key),
and the two required-field errors (id, title).
This commit is contained in:
@@ -360,6 +360,46 @@ class TestGetTvShowInfo:
|
||||
assert info.seasons == ()
|
||||
|
||||
|
||||
class TestGetMovieInfo:
|
||||
"""``get_movie_info`` aggregates ``/movie/{id}`` + external_ids."""
|
||||
|
||||
@patch("alfred.infrastructure.api.tmdb.client.requests.get")
|
||||
def test_happy_path(self, mock_get, client):
|
||||
details = {
|
||||
"id": 27205,
|
||||
"title": "Inception",
|
||||
"release_date": "2010-07-16",
|
||||
}
|
||||
external = {"imdb_id": "tt1375666"}
|
||||
mock_get.side_effect = [
|
||||
_ok_response(details),
|
||||
_ok_response(external),
|
||||
]
|
||||
|
||||
info = client.get_movie_info(27205)
|
||||
|
||||
assert info.tmdb_id == 27205
|
||||
assert info.imdb_id == "tt1375666"
|
||||
assert info.title == "Inception"
|
||||
assert info.release_year == 2010
|
||||
|
||||
@patch("alfred.infrastructure.api.tmdb.client.requests.get")
|
||||
def test_missing_imdb_id_becomes_none(self, mock_get, client):
|
||||
mock_get.side_effect = [
|
||||
_ok_response(
|
||||
{
|
||||
"id": 1,
|
||||
"title": "X",
|
||||
"release_date": "2024-01-01",
|
||||
}
|
||||
),
|
||||
_ok_response({}),
|
||||
]
|
||||
info = client.get_movie_info(1)
|
||||
assert info.imdb_id is None
|
||||
assert info.release_year == 2024
|
||||
|
||||
|
||||
class TestIsConfigured:
|
||||
def test_true_when_complete(self, client):
|
||||
assert client.is_configured() is True
|
||||
|
||||
@@ -13,8 +13,10 @@ from datetime import date
|
||||
import pytest
|
||||
|
||||
from alfred.infrastructure.api.tmdb.dto import (
|
||||
TmdbMovieInfo,
|
||||
TmdbSeasonInfo,
|
||||
TmdbShowInfo,
|
||||
parse_movie_info,
|
||||
parse_tv_show_info,
|
||||
)
|
||||
|
||||
@@ -166,3 +168,90 @@ class TestParseTvShowInfoErrors:
|
||||
{},
|
||||
today=REF_DATE,
|
||||
)
|
||||
|
||||
|
||||
# ════════════════════════════════════════════════════════════════════════════
|
||||
# parse_movie_info
|
||||
# ════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
|
||||
def _movie_details(**overrides):
|
||||
base = {
|
||||
"id": 27205,
|
||||
"title": "Inception",
|
||||
"release_date": "2010-07-16",
|
||||
}
|
||||
base.update(overrides)
|
||||
return base
|
||||
|
||||
|
||||
class TestParseMovieInfoHappyPath:
|
||||
def test_minimal(self):
|
||||
info = parse_movie_info(
|
||||
_movie_details(),
|
||||
{"imdb_id": "tt1375666"},
|
||||
)
|
||||
assert info == TmdbMovieInfo(
|
||||
tmdb_id=27205,
|
||||
imdb_id="tt1375666",
|
||||
title="Inception",
|
||||
release_year=2010,
|
||||
)
|
||||
|
||||
def test_release_year_extracted_from_release_date(self):
|
||||
info = parse_movie_info(
|
||||
_movie_details(release_date="1999-03-31"),
|
||||
{},
|
||||
)
|
||||
assert info.release_year == 1999
|
||||
|
||||
|
||||
class TestParseMovieInfoImdb:
|
||||
def test_missing_imdb_id_becomes_none(self):
|
||||
info = parse_movie_info(_movie_details(), {})
|
||||
assert info.imdb_id is None
|
||||
|
||||
def test_null_imdb_id_becomes_none(self):
|
||||
info = parse_movie_info(_movie_details(), {"imdb_id": None})
|
||||
assert info.imdb_id is None
|
||||
|
||||
def test_empty_string_imdb_id_becomes_none(self):
|
||||
info = parse_movie_info(_movie_details(), {"imdb_id": ""})
|
||||
assert info.imdb_id is None
|
||||
|
||||
|
||||
class TestParseMovieInfoReleaseYear:
|
||||
def test_no_release_date_yields_none(self):
|
||||
info = parse_movie_info(_movie_details(release_date=None), {})
|
||||
assert info.release_year is None
|
||||
|
||||
def test_empty_release_date_yields_none(self):
|
||||
info = parse_movie_info(_movie_details(release_date=""), {})
|
||||
assert info.release_year is None
|
||||
|
||||
def test_release_date_too_short_yields_none(self):
|
||||
info = parse_movie_info(_movie_details(release_date="201"), {})
|
||||
assert info.release_year is None
|
||||
|
||||
def test_release_date_non_numeric_prefix_yields_none(self):
|
||||
info = parse_movie_info(_movie_details(release_date="soon"), {})
|
||||
assert info.release_year is None
|
||||
|
||||
def test_missing_release_date_yields_none(self):
|
||||
# release_date key absent from the payload.
|
||||
info = parse_movie_info({"id": 1, "title": "X"}, {})
|
||||
assert info.release_year is None
|
||||
|
||||
|
||||
class TestParseMovieInfoErrors:
|
||||
def test_missing_id_raises(self):
|
||||
with pytest.raises(ValueError, match="'id'"):
|
||||
parse_movie_info({"title": "X"}, {})
|
||||
|
||||
def test_missing_title_raises(self):
|
||||
with pytest.raises(ValueError, match="'title'"):
|
||||
parse_movie_info({"id": 1}, {})
|
||||
|
||||
def test_empty_title_raises(self):
|
||||
with pytest.raises(ValueError, match="'title'"):
|
||||
parse_movie_info({"id": 1, "title": ""}, {})
|
||||
|
||||
Reference in New Issue
Block a user