261 lines
8.6 KiB
Python
261 lines
8.6 KiB
Python
"""Tests for the pure parsing helpers in ``alfred.infrastructure.api.tmdb.dto``.
|
|
|
|
These tests exercise :func:`parse_tv_show_info` without any HTTP — the
|
|
function takes the raw dicts that the client would otherwise pass after
|
|
deserializing the TMDB JSON response. The reference date is injected so
|
|
the ``aired`` derivation is deterministic.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from datetime import date
|
|
|
|
import pytest
|
|
|
|
from alfred.domain.movies_TO_CHECK.value_objects import MovieTitle, ReleaseYear
|
|
from alfred.domain.shared_TO_CHECK.value_objects import ImdbId, TmdbId
|
|
from alfred.domain.tv_shows.value_objects import ShowStatus
|
|
from alfred.infrastructure.api_TO_CHECK.tmdb.dto import (
|
|
TmdbMovieInfo,
|
|
TmdbSeasonInfo,
|
|
TmdbShowInfo,
|
|
parse_movie_info,
|
|
parse_tv_show_info,
|
|
)
|
|
|
|
REF_DATE = date(2026, 5, 25)
|
|
|
|
|
|
def _details(**overrides):
|
|
base = {
|
|
"id": 84958,
|
|
"name": "Foundation",
|
|
"status": "Returning Series",
|
|
"seasons": [],
|
|
}
|
|
base.update(overrides)
|
|
return base
|
|
|
|
|
|
class TestParseTvShowInfoHappyPath:
|
|
def test_minimal(self):
|
|
info = parse_tv_show_info(
|
|
_details(),
|
|
{"imdb_id": "tt0804484"},
|
|
today=REF_DATE,
|
|
)
|
|
assert info == TmdbShowInfo(
|
|
tmdb_id=TmdbId(84958),
|
|
imdb_id=ImdbId("tt0804484"),
|
|
name="Foundation",
|
|
status=ShowStatus.RETURNING_SERIES,
|
|
seasons=(),
|
|
)
|
|
|
|
def test_with_seasons(self):
|
|
info = parse_tv_show_info(
|
|
_details(
|
|
seasons=[
|
|
{"season_number": 1, "episode_count": 10, "air_date": "2021-09-24"},
|
|
{"season_number": 2, "episode_count": 10, "air_date": "2023-07-14"},
|
|
{"season_number": 3, "episode_count": 10, "air_date": "2027-01-01"},
|
|
],
|
|
),
|
|
{"imdb_id": "tt0804484"},
|
|
today=REF_DATE,
|
|
)
|
|
assert info.seasons == (
|
|
TmdbSeasonInfo(number=1, episode_count=10, aired=True),
|
|
TmdbSeasonInfo(number=2, episode_count=10, aired=True),
|
|
TmdbSeasonInfo(number=3, episode_count=10, aired=False),
|
|
)
|
|
|
|
|
|
class TestParseTvShowInfoImdb:
|
|
def test_missing_imdb_id_becomes_none(self):
|
|
info = parse_tv_show_info(_details(), {}, today=REF_DATE)
|
|
assert info.imdb_id is None
|
|
|
|
def test_null_imdb_id_becomes_none(self):
|
|
info = parse_tv_show_info(_details(), {"imdb_id": None}, today=REF_DATE)
|
|
assert info.imdb_id is None
|
|
|
|
def test_empty_string_imdb_id_becomes_none(self):
|
|
info = parse_tv_show_info(_details(), {"imdb_id": ""}, today=REF_DATE)
|
|
assert info.imdb_id is None
|
|
|
|
|
|
class TestParseTvShowInfoAired:
|
|
def test_air_date_today_counts_as_aired(self):
|
|
info = parse_tv_show_info(
|
|
_details(
|
|
seasons=[{"season_number": 1, "episode_count": 1, "air_date": "2026-05-25"}],
|
|
),
|
|
{},
|
|
today=REF_DATE,
|
|
)
|
|
assert info.seasons[0].aired is True
|
|
|
|
def test_air_date_tomorrow_not_aired(self):
|
|
info = parse_tv_show_info(
|
|
_details(
|
|
seasons=[{"season_number": 1, "episode_count": 1, "air_date": "2026-05-26"}],
|
|
),
|
|
{},
|
|
today=REF_DATE,
|
|
)
|
|
assert info.seasons[0].aired is False
|
|
|
|
def test_no_air_date_not_aired(self):
|
|
info = parse_tv_show_info(
|
|
_details(
|
|
seasons=[{"season_number": 1, "episode_count": 1}],
|
|
),
|
|
{},
|
|
today=REF_DATE,
|
|
)
|
|
assert info.seasons[0].aired is False
|
|
|
|
def test_empty_air_date_not_aired(self):
|
|
info = parse_tv_show_info(
|
|
_details(
|
|
seasons=[{"season_number": 1, "episode_count": 1, "air_date": ""}],
|
|
),
|
|
{},
|
|
today=REF_DATE,
|
|
)
|
|
assert info.seasons[0].aired is False
|
|
|
|
def test_malformed_air_date_not_aired(self):
|
|
info = parse_tv_show_info(
|
|
_details(
|
|
seasons=[{"season_number": 1, "episode_count": 1, "air_date": "soon"}],
|
|
),
|
|
{},
|
|
today=REF_DATE,
|
|
)
|
|
assert info.seasons[0].aired is False
|
|
|
|
|
|
class TestParseTvShowInfoErrors:
|
|
def test_missing_id_raises(self):
|
|
with pytest.raises(ValueError, match="'id'"):
|
|
parse_tv_show_info({"name": "X", "status": "Ended"}, {}, today=REF_DATE)
|
|
|
|
def test_missing_name_raises(self):
|
|
with pytest.raises(ValueError, match="'name'"):
|
|
parse_tv_show_info({"id": 1, "status": "Ended"}, {}, today=REF_DATE)
|
|
|
|
def test_empty_name_raises(self):
|
|
with pytest.raises(ValueError, match="'name'"):
|
|
parse_tv_show_info(
|
|
{"id": 1, "name": "", "status": "Ended"}, {}, today=REF_DATE
|
|
)
|
|
|
|
def test_missing_status_raises(self):
|
|
with pytest.raises(ValueError, match="'status'"):
|
|
parse_tv_show_info({"id": 1, "name": "X"}, {}, today=REF_DATE)
|
|
|
|
def test_season_missing_number_raises(self):
|
|
with pytest.raises(ValueError, match="season_number"):
|
|
parse_tv_show_info(
|
|
_details(seasons=[{"episode_count": 5}]),
|
|
{},
|
|
today=REF_DATE,
|
|
)
|
|
|
|
def test_season_missing_episode_count_raises(self):
|
|
with pytest.raises(ValueError, match="episode_count"):
|
|
parse_tv_show_info(
|
|
_details(seasons=[{"season_number": 1}]),
|
|
{},
|
|
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=TmdbId(27205),
|
|
imdb_id=ImdbId("tt1375666"),
|
|
title=MovieTitle("Inception"),
|
|
release_year=ReleaseYear(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 == ReleaseYear(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": ""}, {})
|