Files
Aurora-Core/tests/integration/test_movie_ingestion.py
T

190 lines
6.5 KiB
Python
Raw Normal View History

2026-09-04 17:34:03 -05:00
"""Integration tests for the movie ingestion pipeline (section 15): a fake
provider stands in for TMDb so these run without network access, while
still exercising the real normalize -> match -> upsert -> provenance path
against PostgreSQL."""
from __future__ import annotations
2026-09-05 08:16:18 -05:00
import uuid
2026-09-04 17:34:03 -05:00
from typing import Any
import pytest
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession
from app.core.ids import aurora_id
2026-09-05 07:46:30 -05:00
from app.models.artwork import Artwork
from app.models.company import Company
2026-09-04 17:34:03 -05:00
from app.models.credit import Credit
from app.models.genre import Genre
from app.models.movie import Movie
from app.models.provider import ProviderRecord
from app.providers.base import MetadataProvider
from app.services import movie_service
2026-09-04 17:34:03 -05:00
from app.services.ingestion.movie_ingestion import ingest_movie
from tests.unit.test_normalizer import FAKE_MOVIE
class FakeTMDbProvider(MetadataProvider):
slug = "tmdb"
name = "The Movie Database (TMDb)"
def __init__(self, payload: dict[str, Any]) -> None:
self._payload = payload
async def get_movie(self, external_id: str) -> dict[str, Any]:
return self._payload
async def get_series(self, external_id: str) -> dict[str, Any]:
raise NotImplementedError
async def get_season(self, series_external_id: str, season_number: int) -> dict[str, Any]:
raise NotImplementedError
async def get_person(self, external_id: str) -> dict[str, Any]:
2026-09-05 08:37:27 -05:00
raise NotImplementedError
async def get_collection(self, external_id: str) -> dict[str, Any]:
2026-09-04 17:34:03 -05:00
raise NotImplementedError
async def search_movie(self, query: str) -> list[dict[str, Any]]:
raise NotImplementedError
@pytest.mark.asyncio
async def test_ingest_movie_creates_normalized_record(db_session: AsyncSession):
provider = FakeTMDbProvider(FAKE_MOVIE)
movie_id = await ingest_movie(db_session, provider, "603")
await db_session.commit()
movie = await db_session.get(Movie, movie_id)
assert movie is not None
assert movie.title == "The Matrix"
assert movie.runtime_minutes == 136
genres = (await db_session.execute(select(Genre.name))).scalars().all()
assert "Action" in genres
credits_ = (
(await db_session.execute(select(Credit).where(Credit.entity_id == movie_id)))
.scalars()
.all()
)
assert any(c.character == "Neo" for c in credits_)
record = (
await db_session.execute(
select(ProviderRecord).where(ProviderRecord.external_id == "603")
)
).scalar_one()
assert record.aurora_entity_id == movie_id
assert record.match_confidence.value == "new"
@pytest.mark.asyncio
async def test_ingest_movie_is_idempotent(db_session: AsyncSession):
"""Re-running ingestion for the same provider + external_id must update
the same Aurora movie, never create a duplicate (section 2: zero
duplicate ingestion records)."""
provider = FakeTMDbProvider(FAKE_MOVIE)
first_id = await ingest_movie(db_session, provider, "603")
await db_session.commit()
updated_payload = {**FAKE_MOVIE, "title": "The Matrix (Updated Title)"}
second_provider = FakeTMDbProvider(updated_payload)
second_id = await ingest_movie(db_session, second_provider, "603")
await db_session.commit()
assert first_id == second_id
all_movies = (await db_session.execute(select(Movie))).scalars().all()
assert len(all_movies) == 1
assert all_movies[0].title == "The Matrix (Updated Title)"
credits_ = (
(await db_session.execute(select(Credit).where(Credit.entity_id == first_id)))
.scalars()
.all()
)
# Credits are replaced wholesale on re-sync, not duplicated.
assert len(credits_) == 2 # 1 cast + 1 crew from FAKE_MOVIE
2026-09-05 07:46:30 -05:00
@pytest.mark.asyncio
async def test_ingest_movie_ingests_company_logo(db_session: AsyncSession):
provider = FakeTMDbProvider(FAKE_MOVIE)
await ingest_movie(db_session, provider, "603")
await db_session.commit()
company = (
await db_session.execute(
select(Company).where(Company.name == "Village Roadshow Pictures")
)
).scalar_one()
logo = (
await db_session.execute(
select(Artwork).where(Artwork.entity_type == "company", Artwork.entity_id == company.id)
)
).scalar_one()
assert logo.artwork_type.value == "logo"
assert logo.is_primary is True
assert logo.original_url.endswith("/village-roadshow-logo.png")
@pytest.mark.asyncio
async def test_movie_detail_surfaces_company_logo_path(db_session: AsyncSession):
"""The ingested logo must actually reach the API response -- movie_service
used to hardcode companies[].logo_path = None instead of looking up the
company's artwork, so ingestion could succeed while the API still showed
logo_path: None for every company."""
provider = FakeTMDbProvider(FAKE_MOVIE)
movie_id = await ingest_movie(db_session, provider, "603")
await db_session.commit()
detail = await movie_service.get_movie_detail(db_session, aurora_id("movie", str(movie_id)))
roadshow = next(c for c in detail.companies if c.name == "Village Roadshow Pictures")
assert roadshow.logo_path is not None
assert roadshow.logo_path.endswith("/village-roadshow-logo.png")
2026-09-05 08:16:18 -05:00
@pytest.mark.asyncio
async def test_movie_recommendations_only_include_already_ingested_movies(
db_session: AsyncSession,
):
"""TMDb's recommendations list can reference movies Aurora hasn't
ingested yet; ingest_movie must link only the ones it can resolve by
external id (section 15: never fabricate a stub for an unseen entity),
and GET .../recommendations must return them in TMDb's ranked order."""
sequel_payload = {**FAKE_MOVIE, "id": 604, "title": "The Matrix Reloaded"}
await ingest_movie(db_session, FakeTMDbProvider(sequel_payload), "604")
await db_session.flush()
movie_with_recs = {
**FAKE_MOVIE,
"recommendations": {"results": [{"id": 604}, {"id": 999999}]},
}
movie_id = await ingest_movie(db_session, FakeTMDbProvider(movie_with_recs), "603")
await db_session.commit()
recs = await movie_service.get_movie_recommendations(
db_session, aurora_id("movie", str(movie_id))
)
assert [r.title for r in recs.recommendations] == ["The Matrix Reloaded"]
@pytest.mark.asyncio
async def test_movie_recommendations_not_found_for_unknown_movie(db_session: AsyncSession):
with pytest.raises(movie_service.MovieNotFoundError):
await movie_service.get_movie_recommendations(
db_session, aurora_id("movie", str(uuid.uuid4()))
)