Fix movie/series API responses never surfacing company logo_path
CI / lint (push) Canceled after 0s
CI / typecheck (push) Canceled after 0s
CI / unit-tests (push) Canceled after 0s
CI / integration-tests (push) Canceled after 0s
CI / container-build (push) Canceled after 0s
CI / dependency-audit (push) Canceled after 0s

movie_service.get_movie_detail() hardcoded companies[].logo_path = None
instead of looking up the company's logo artwork, and tv_service's
networks[]/companies[] never set logo_path at all -- both just built
CompanyBriefOut without the artwork lookup that company_service.py (and
_season_brief for posters) already does correctly. Ingestion and storage
were working the whole time; the API layer just never read the result.

Co-Authored-By: Claude Sonnet 5 <[email protected]>
This commit is contained in:
NixReaper
2026-09-05 08:01:56 -05:00
co-authored by Claude Sonnet 5
parent 2138d4afc3
commit 73488a0d80
4 changed files with 69 additions and 21 deletions
+13 -9
View File
@@ -47,6 +47,18 @@ async def get_movie_detail(session: AsyncSession, movie_aurora_id: str) -> Movie
cast, crew = split_credits(credits_) cast, crew = split_credits(credits_)
company_out: list[CompanyBriefOut] = []
for company, _role in companies:
logos = await polymorphic.get_artwork(session, "company", company.id, ArtworkType.LOGO)
company_out.append(
CompanyBriefOut(
id=aurora_id("company", str(company.id)),
name=company.name,
company_type=company.company_type.value,
logo_path=primary_artwork_path(logos, ArtworkType.LOGO),
)
)
return MovieDetailOut( return MovieDetailOut(
id=aurora_id("movie", str(movie.id)), id=aurora_id("movie", str(movie.id)),
title=movie.title, title=movie.title,
@@ -67,15 +79,7 @@ async def get_movie_detail(session: AsyncSession, movie_aurora_id: str) -> Movie
keywords=[KeywordOut(id=aurora_id("keyword", str(k.id)), name=k.name) for k in keywords], keywords=[KeywordOut(id=aurora_id("keyword", str(k.id)), name=k.name) for k in keywords],
cast=cast, cast=cast,
crew=crew, crew=crew,
companies=[ companies=company_out,
CompanyBriefOut(
id=aurora_id("company", str(company.id)),
name=company.name,
company_type=company.company_type.value,
logo_path=None,
)
for company, _role in companies
],
collection=( collection=(
CollectionBriefOut( CollectionBriefOut(
id=aurora_id("collection", str(movie.collection.id)), name=movie.collection.name id=aurora_id("collection", str(movie.collection.id)), name=movie.collection.name
+14 -12
View File
@@ -53,6 +53,16 @@ async def _season_brief(session: AsyncSession, season) -> SeasonBriefOut:
) )
async def _company_brief(session: AsyncSession, company) -> CompanyBriefOut:
logos = await polymorphic.get_artwork(session, "company", company.id, ArtworkType.LOGO)
return CompanyBriefOut(
id=aurora_id("company", str(company.id)),
name=company.name,
company_type=company.company_type.value,
logo_path=primary_artwork_path(logos, ArtworkType.LOGO),
)
async def get_series_detail(session: AsyncSession, series_aurora_id: str) -> SeriesDetailOut: async def get_series_detail(session: AsyncSession, series_aurora_id: str) -> SeriesDetailOut:
series_id = _resolve_id(series_aurora_id, "series", SeriesNotFoundError) series_id = _resolve_id(series_aurora_id, "series", SeriesNotFoundError)
series = await tv_repository.get_series_by_id(session, series_id) series = await tv_repository.get_series_by_id(session, series_id)
@@ -75,6 +85,8 @@ async def get_series_detail(session: AsyncSession, series_aurora_id: str) -> Ser
_, creators = split_credits([c for c in credits_ if c.department == "Creator"]) _, creators = split_credits([c for c in credits_ if c.department == "Creator"])
season_briefs = [await _season_brief(session, s) for s in seasons] season_briefs = [await _season_brief(session, s) for s in seasons]
network_briefs = [await _company_brief(session, c) for c, _role in networks]
company_briefs = [await _company_brief(session, c) for c, _role in companies]
return SeriesDetailOut( return SeriesDetailOut(
id=aurora_id("series", str(series.id)), id=aurora_id("series", str(series.id)),
@@ -96,18 +108,8 @@ async def get_series_detail(session: AsyncSession, series_aurora_id: str) -> Ser
creators=creators, creators=creators,
cast=cast, cast=cast,
crew=crew, crew=crew,
networks=[ networks=network_briefs,
CompanyBriefOut( companies=company_briefs,
id=aurora_id("company", str(c.id)), name=c.name, company_type=c.company_type.value
)
for c, _role in networks
],
companies=[
CompanyBriefOut(
id=aurora_id("company", str(c.id)), name=c.name, company_type=c.company_type.value
)
for c, _role in companies
],
seasons=season_briefs, seasons=season_briefs,
images=to_images_out(artwork), images=to_images_out(artwork),
videos=[to_video_out(v) for v in videos], videos=[to_video_out(v) for v in videos],
+20
View File
@@ -11,6 +11,7 @@ import pytest
from sqlalchemy import select from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.ext.asyncio import AsyncSession
from app.core.ids import aurora_id
from app.models.artwork import Artwork from app.models.artwork import Artwork
from app.models.company import Company from app.models.company import Company
from app.models.credit import Credit from app.models.credit import Credit
@@ -18,6 +19,7 @@ from app.models.genre import Genre
from app.models.movie import Movie from app.models.movie import Movie
from app.models.provider import ProviderRecord from app.models.provider import ProviderRecord
from app.providers.base import MetadataProvider from app.providers.base import MetadataProvider
from app.services import movie_service
from app.services.ingestion.movie_ingestion import ingest_movie from app.services.ingestion.movie_ingestion import ingest_movie
from tests.unit.test_normalizer import FAKE_MOVIE from tests.unit.test_normalizer import FAKE_MOVIE
@@ -128,3 +130,21 @@ async def test_ingest_movie_ingests_company_logo(db_session: AsyncSession):
assert logo.artwork_type.value == "logo" assert logo.artwork_type.value == "logo"
assert logo.is_primary is True assert logo.is_primary is True
assert logo.original_url.endswith("/village-roadshow-logo.png") 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")
@@ -11,8 +11,10 @@ import pytest
from sqlalchemy import select from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.ext.asyncio import AsyncSession
from app.core.ids import aurora_id
from app.models.tv import Episode, Season, Series from app.models.tv import Episode, Season, Series
from app.providers.base import MetadataProvider, ProviderNotFoundError from app.providers.base import MetadataProvider, ProviderNotFoundError
from app.services import tv_service
from app.services.ingestion.series_ingestion import ingest_season, ingest_series from app.services.ingestion.series_ingestion import ingest_season, ingest_series
from tests.unit.test_normalizer import FAKE_SEASON, FAKE_SERIES from tests.unit.test_normalizer import FAKE_SEASON, FAKE_SERIES
@@ -115,3 +117,23 @@ async def test_ingest_season_is_idempotent(db_session: AsyncSession):
) )
# Re-running must not duplicate episode rows. # Re-running must not duplicate episode rows.
assert len(episodes) == 2 assert len(episodes) == 2
@pytest.mark.asyncio
async def test_series_detail_surfaces_network_logo_path(db_session: AsyncSession):
"""Same class of bug as movie companies: tv_service used to build
networks[]/companies[] without ever looking up their logo artwork, so
logo_path was always None regardless of what ingestion had stored."""
provider = FakeTMDbSeriesProvider(FAKE_SERIES, FAKE_SEASON)
series_id = await ingest_series(db_session, provider, "1396")
await db_session.commit()
detail = await tv_service.get_series_detail(db_session, aurora_id("series", str(series_id)))
amc = next(n for n in detail.networks if n.name == "AMC")
assert amc.logo_path is not None
assert amc.logo_path.endswith("/amc-logo.png")
gran_via = next(c for c in detail.companies if c.name == "Gran Via Productions")
assert gran_via.logo_path is None