"""Unit tests for the TMDb normalizer (app/services/metadata/normalizer.py) — section 13. Uses hand-built payload fragments shaped like real TMDb responses rather than calling the network.""" from __future__ import annotations from datetime import date from app.services.metadata.normalizer import ( normalize_movie, normalize_person, normalize_season, normalize_series, ) FAKE_MOVIE = { "id": 603, "title": "The Matrix", "original_title": "The Matrix", "overview": "A computer hacker learns...", "tagline": "Welcome to the Real World", "status": "Released", "original_language": "en", "release_date": "1999-03-30", "runtime": 136, "budget": 63000000, "revenue": 463517383, "popularity": 45.5, "vote_average": 8.2, "vote_count": 24000, "adult": False, "imdb_id": "tt0133093", "genres": [{"id": 28, "name": "Action"}, {"id": 878, "name": "Science Fiction"}], "production_companies": [ {"id": 79, "name": "Village Roadshow Pictures", "origin_country": "US"} ], "poster_path": "/poster.jpg", "backdrop_path": "/backdrop.jpg", "belongs_to_collection": {"id": 2344, "name": "The Matrix Collection"}, "credits": { "cast": [ { "id": 6384, "name": "Keanu Reeves", "character": "Neo", "order": 0, "profile_path": "/keanu.jpg", "gender": 2, } ], "crew": [ { "id": 9339, "name": "Lana Wachowski", "department": "Directing", "job": "Director", "gender": 1, } ], }, "images": { "posters": [ {"file_path": "/poster.jpg", "width": 2000, "height": 3000, "aspect_ratio": 0.667, "iso_639_1": "en", "vote_average": 8.1} ], "backdrops": [], "logos": [], }, "videos": { "results": [ { "id": "abc123", "type": "Trailer", "site": "YouTube", "key": "vKQi3bBA1y8", "iso_639_1": "en", "iso_3166_1": "US", "official": True, "published_at": "1999-03-01T00:00:00.000Z", } ] }, "keywords": {"keywords": [{"id": 1, "name": "dystopia"}]}, "alternative_titles": {"titles": [{"title": "Matrix", "iso_3166_1": "US"}]}, "external_ids": {"imdb_id": "tt0133093"}, "recommendations": {"results": [{"id": 604}]}, } FAKE_PERSON = { "id": 6384, "name": "Keanu Reeves", "biography": "Keanu Charles Reeves is a Canadian actor.", "birthday": "1964-09-02", "deathday": None, "place_of_birth": "Beirut, Lebanon", "gender": 2, "known_for_department": "Acting", "popularity": 30.2, "also_known_as": ["Keanu Charles Reeves"], "profile_path": "/keanu.jpg", "images": {"profiles": [{"file_path": "/keanu.jpg", "width": 1000, "height": 1500, "aspect_ratio": 0.667, "iso_639_1": None, "vote_average": 5.0}]}, "external_ids": {"imdb_id": "nm0000206"}, } def test_normalize_movie_maps_core_fields(): movie = normalize_movie(FAKE_MOVIE) assert movie.external_id == "603" assert movie.title == "The Matrix" assert movie.status == "released" assert movie.release_date == date(1999, 3, 30) assert movie.runtime_minutes == 136 assert movie.budget == 63000000 assert movie.adult is False def test_normalize_movie_maps_genres_and_keywords(): movie = normalize_movie(FAKE_MOVIE) assert movie.genres == ["Action", "Science Fiction"] assert movie.keywords == ["dystopia"] assert movie.alternate_titles == ["Matrix"] def test_normalize_movie_maps_cast_and_crew(): movie = normalize_movie(FAKE_MOVIE) assert len(movie.cast) == 1 assert movie.cast[0].external_id == "6384" assert movie.cast[0].department == "Acting" assert movie.cast[0].character == "Neo" assert movie.cast[0].gender == "male" assert len(movie.crew) == 1 assert movie.crew[0].department == "Directing" assert movie.crew[0].job == "Director" assert movie.crew[0].gender == "female" def test_normalize_movie_maps_artwork_and_videos(): movie = normalize_movie(FAKE_MOVIE) poster_types = [a.artwork_type for a in movie.artwork] assert poster_types.count("poster") >= 1 assert all(a.url.startswith("https://image.tmdb.org/t/p/original/") for a in movie.artwork) assert len(movie.videos) == 1 assert movie.videos[0].url == "https://www.youtube.com/watch?v=vKQi3bBA1y8" assert movie.videos[0].is_official is True def test_normalize_movie_external_ids_never_include_tmdb_itself(): # TMDb's own id is handled separately by the ingestion pipeline via # `normalized.external_id`, not folded into `external_ids` (section 3: # Aurora doesn't conflate its own identity with provider identity). movie = normalize_movie(FAKE_MOVIE) assert "tmdb" not in movie.external_ids assert movie.external_ids["imdb"] == "tt0133093" def test_normalize_movie_maps_collection_and_recommendations(): movie = normalize_movie(FAKE_MOVIE) assert movie.collection == ("2344", "The Matrix Collection") assert movie.recommendation_external_ids == ["604"] def test_normalize_movie_handles_missing_optional_sections(): minimal = {"id": 1, "title": "Minimal Movie"} movie = normalize_movie(minimal) assert movie.title == "Minimal Movie" assert movie.genres == [] assert movie.cast == [] assert movie.artwork == [] assert movie.collection is None def test_normalize_person_maps_core_fields(): person = normalize_person(FAKE_PERSON) assert person.external_id == "6384" assert person.name == "Keanu Reeves" assert person.birthday == date(1964, 9, 2) assert person.deathday is None assert person.gender == "male" assert person.external_ids["imdb"] == "nm0000206" assert "Keanu Charles Reeves" in person.aliases def test_normalize_person_profile_artwork_deduplicated(): person = normalize_person(FAKE_PERSON) # profile_path duplicates an entry already in images.profiles; it must # not be inserted twice. paths = [a.provider_asset_id for a in person.artwork] assert paths.count("/keanu.jpg") == 1 FAKE_SERIES = { "id": 1396, "name": "Ozymandias", "original_name": "Ozymandias", "overview": "A chemistry teacher turns to crime.", "tagline": "Change the equation.", "status": "Ended", "original_language": "en", "first_air_date": "2008-01-20", "last_air_date": "2013-09-29", "number_of_seasons": 5, "number_of_episodes": 62, "popularity": 120.5, "vote_average": 8.9, "vote_count": 15000, "genres": [{"id": 18, "name": "Drama"}, {"id": 80, "name": "Crime"}], "created_by": [{"id": 66633, "name": "Vince Gilligan", "gender": 2}], "networks": [{"id": 174, "name": "AMC", "origin_country": "US"}], "production_companies": [ {"id": 2605, "name": "Gran Via Productions", "origin_country": "US"} ], "poster_path": "/poster.jpg", "backdrop_path": "/backdrop.jpg", "credits": { "cast": [ { "id": 17419, "name": "Bryan Cranston", "character": "Walter White", "order": 0, "gender": 2, } ], "crew": [], }, "images": {"posters": [], "backdrops": [], "logos": []}, "videos": {"results": []}, "keywords": {"results": [{"id": 1, "name": "drug dealer"}]}, "alternative_titles": {"results": [{"title": "Breaking Bad", "iso_3166_1": "US"}]}, "external_ids": {"imdb_id": "tt0903747", "tvdb_id": 81189}, "seasons": [ { "season_number": 1, "name": "Season 1", "overview": "The first season.", "air_date": "2008-01-20", "episode_count": 7, "poster_path": "/season1.jpg", }, { # TMDb includes a "specials" season with number 0 — must not be # dropped. "season_number": 0, "name": "Specials", "overview": None, "air_date": None, "episode_count": 3, "poster_path": None, }, ], } FAKE_SEASON = { "season_number": 1, "name": "Season 1", "overview": "The first season.", "air_date": "2008-01-20", "poster_path": "/season1.jpg", "images": {"posters": []}, "external_ids": {"tvdb_id": 12345}, "episodes": [ { "id": 62085, "episode_number": 1, "name": "Pilot", "overview": "Walter White begins his transformation.", "air_date": "2008-01-20", "runtime": 58, "still_path": "/still1.jpg", "crew": [ {"id": 66633, "name": "Vince Gilligan", "department": "Directing", "job": "Director", "gender": 2} ], "guest_stars": [ {"id": 99999, "name": "Some Guest", "character": "Random Guy", "order": 5, "gender": 2} ], }, { "id": 62086, "episode_number": 2, "name": "Cat's in the Bag...", "overview": None, "air_date": "2008-01-27", "runtime": 48, "still_path": None, "crew": [], "guest_stars": [], }, ], } def test_normalize_series_maps_core_fields(): series = normalize_series(FAKE_SERIES) assert series.external_id == "1396" assert series.title == "Ozymandias" assert series.status == "ended" assert series.first_air_date == date(2008, 1, 20) assert series.number_of_seasons == 5 def test_normalize_series_maps_networks_vs_production_companies(): series = normalize_series(FAKE_SERIES) assert [n.name for n in series.networks] == ["AMC"] assert [c.name for c in series.companies] == ["Gran Via Productions"] def test_normalize_series_maps_creators_separately_from_crew(): series = normalize_series(FAKE_SERIES) assert len(series.creators) == 1 assert series.creators[0].name == "Vince Gilligan" assert series.creators[0].department == "Creator" assert all(c.department != "Creator" for c in series.crew) def test_normalize_series_keywords_and_alt_titles_use_tv_shaped_keys(): # TMDb nests these under "results" for /tv, unlike "keywords"/"titles" # for /movie — a normalizer bug here would silently return empty lists. series = normalize_series(FAKE_SERIES) assert series.keywords == ["drug dealer"] assert series.alternate_titles == ["Breaking Bad"] def test_normalize_series_keeps_season_zero_specials(): series = normalize_series(FAKE_SERIES) season_numbers = sorted(s.season_number for s in series.seasons) assert season_numbers == [0, 1] def test_normalize_series_external_ids_include_tvdb(): series = normalize_series(FAKE_SERIES) assert series.external_ids["imdb"] == "tt0903747" assert series.external_ids["tvdb"] == "81189" def test_normalize_season_maps_episodes(): season = normalize_season(FAKE_SEASON) assert season.season_number == 1 assert len(season.episodes) == 2 assert season.episodes[0].external_id == "62085" assert season.episodes[0].title == "Pilot" assert season.episodes[0].runtime_minutes == 58 def test_normalize_season_separates_guest_stars_from_main_cast(): season = normalize_season(FAKE_SEASON) episode = season.episodes[0] assert episode.cast == [] assert len(episode.guest_stars) == 1 assert episode.guest_stars[0].character == "Random Guy" def test_normalize_season_maps_episode_crew_and_stills(): season = normalize_season(FAKE_SEASON) episode = season.episodes[0] assert episode.crew[0].job == "Director" assert len(episode.stills) == 1 assert episode.stills[0].artwork_type == "still" def test_normalize_season_handles_episode_without_still(): season = normalize_season(FAKE_SEASON) assert season.episodes[1].stills == []