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

191 lines
6.5 KiB
Python
Raw Normal View History

2026-09-05 08:37:27 -05:00
"""Integration tests for collection/franchise ingestion (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
from typing import Any
import pytest
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession
from app.core.ids import aurora_id
from app.models.artwork import Artwork
from app.models.collection import Collection, CollectionMovie
from app.providers.base import MetadataProvider
from app.services import collection_service
from app.services.ingestion.collection_ingestion import ingest_collection
from app.services.ingestion.movie_ingestion import ingest_movie
from tests.unit.test_normalizer import FAKE_COLLECTION, FAKE_MOVIE
class FakeTMDbCollectionProvider(MetadataProvider):
slug = "tmdb"
name = "The Movie Database (TMDb)"
def __init__(
self, movie_payloads: dict[str, dict[str, Any]], collection_payload: dict[str, Any]
) -> None:
self._movie_payloads = movie_payloads
self._collection_payload = collection_payload
async def get_movie(self, external_id: str) -> dict[str, Any]:
return self._movie_payloads[external_id]
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]:
raise NotImplementedError
async def get_collection(self, external_id: str) -> dict[str, Any]:
return self._collection_payload
async def search_movie(self, query: str) -> list[dict[str, Any]]:
raise NotImplementedError
@pytest.mark.asyncio
async def test_ingest_collection_only_links_already_ingested_movies(db_session: AsyncSession):
"""Two of FAKE_COLLECTION's four parts (603, 604) are ingested first;
605 and 999 are not -- CollectionMovie must link only the two Aurora
already knows about, in release-date order, never fabricate a stub for
the other two (section 15)."""
# 604's videos overridden to empty: FAKE_MOVIE's embedded video has a
# fixed provider_video_id, and two different movies both ingesting it
# would collide on the real uq_video_provider_source unique constraint.
2026-09-05 08:37:27 -05:00
provider = FakeTMDbCollectionProvider(
movie_payloads={
"603": {**FAKE_MOVIE, "id": 603, "title": "The Matrix"},
"604": {
**FAKE_MOVIE,
"id": 604,
"title": "The Matrix Reloaded",
"videos": {"results": []},
},
2026-09-05 08:37:27 -05:00
},
collection_payload=FAKE_COLLECTION,
)
await ingest_movie(db_session, provider, "603")
await ingest_movie(db_session, provider, "604")
await db_session.flush()
collection_id = await ingest_collection(db_session, provider, "2344")
await db_session.commit()
collection = await db_session.get(Collection, collection_id)
assert collection is not None
assert collection.name == "The Matrix Collection"
assert collection.overview == "A group of films..."
links = (
(
await db_session.execute(
select(CollectionMovie)
.where(CollectionMovie.collection_id == collection_id)
.order_by(CollectionMovie.sort_order.asc())
)
)
.scalars()
.all()
)
assert len(links) == 2
detail = await collection_service.get_collection_detail(
db_session, aurora_id("collection", str(collection_id))
)
assert [m.title for m in detail.movies] == ["The Matrix", "The Matrix Reloaded"]
assert detail.poster_path is not None
assert detail.poster_path.endswith("/collection-poster.jpg")
@pytest.mark.asyncio
async def test_ingest_collection_is_idempotent_and_does_not_duplicate_links(
db_session: AsyncSession,
):
provider = FakeTMDbCollectionProvider(
movie_payloads={"603": {**FAKE_MOVIE, "id": 603, "title": "The Matrix"}},
collection_payload=FAKE_COLLECTION,
)
await ingest_movie(db_session, provider, "603")
await db_session.flush()
first_id = await ingest_collection(db_session, provider, "2344")
await db_session.flush()
second_id = await ingest_collection(db_session, provider, "2344")
await db_session.commit()
assert first_id == second_id
all_collections = (await db_session.execute(select(Collection))).scalars().all()
assert len(all_collections) == 1
links = (
(
await db_session.execute(
select(CollectionMovie).where(CollectionMovie.collection_id == first_id)
)
)
.scalars()
.all()
)
assert len(links) == 1
@pytest.mark.asyncio
async def test_ingest_collection_does_not_clobber_overview_from_movie_stub(
db_session: AsyncSession,
):
"""A movie referencing this collection via belongs_to_collection creates
a bare stub (name only, overview=None) *after* the real ingest_collection
already ran -- get_or_create_collection must not blank the real overview
back to None just because the stub call has none to offer."""
provider = FakeTMDbCollectionProvider(
movie_payloads={
"603": {**FAKE_MOVIE, "id": 603, "title": "The Matrix"},
"604": {**FAKE_MOVIE, "id": 604, "title": "The Matrix Reloaded"},
},
collection_payload=FAKE_COLLECTION,
)
collection_id = await ingest_collection(db_session, provider, "2344")
await db_session.flush()
await ingest_movie(db_session, provider, "604")
await db_session.commit()
collection = await db_session.get(Collection, collection_id)
assert collection.overview == "A group of films..."
@pytest.mark.asyncio
async def test_ingest_collection_ingests_poster_and_backdrop_artwork(db_session: AsyncSession):
provider = FakeTMDbCollectionProvider(movie_payloads={}, collection_payload=FAKE_COLLECTION)
collection_id = await ingest_collection(db_session, provider, "2344")
await db_session.commit()
artwork = (
(
await db_session.execute(
select(Artwork).where(
Artwork.entity_type == "collection", Artwork.entity_id == collection_id
)
)
)
.scalars()
.all()
)
types = {a.artwork_type.value for a in artwork}
assert types == {"poster", "backdrop"}
assert all(a.is_primary for a in artwork)