fix(e5): address code-review findings (TMDB guid media_type, search ingest guard, discover keying)

Follow-ups from the epic-end /code-review of the E5 branch:

- tmdb.resolve_recs + people._resolve_known_for: match recommendations/
  credits to the library bucketed by media_type. TMDB movie ids and tv ids
  are independent sequences, so a bare `tmdb:<id>` match could deep-link a
  movie rec to an unrelated show (or vice-versa) on a numeric collision.
  Regression test added (test_resolve_recs_matches_by_media_type_not_bare_id).
- routes/search: wrap the _ingest_candidates call so a DB/enrich failure
  degrades to a 422 with a message instead of a bare 500 hidden behind the
  FE's generic "search failed" (extends the B-core-1 rationale); rollback on
  failure. Stamp enriched_at with its own timestamp taken after the enrich,
  not the stub-insert placeholder.
- routes/plex/discover: key serialized cards back by rating_key (globally
  unique in Plex) instead of id(obj); fix the stale rows 3-tuple annotation.
This commit is contained in:
2026-08-21 03:16:24 +02:00
parent ff45db70ae
commit 2b5de89eda
5 changed files with 118 additions and 42 deletions
+42
View File
@@ -212,3 +212,45 @@ def test_enrich_batch_skips_shows_without_guid(db):
assert tmdb.enrich_batch(db, FakeTmdb(), 50) == 0
db.refresh(sh)
assert sh.tmdb_enriched_at is None
def test_resolve_recs_matches_by_media_type_not_bare_id(db):
# TMDB movie ids and tv ids are independent, so tmdb:500 can be BOTH a show (in our library) and a
# movie recommendation. The match must respect media_type — a bare-id match would deep-link the movie
# rec to the unrelated show (and vice-versa).
mlib = _lib(db, key="10", kind="movie")
slib = _lib(db, key="11", kind="show")
show500 = PlexShow(rating_key="s500", library_id=slib.id, title="Show 500", guid="tmdb:500")
movie900 = PlexItem(
rating_key="m900", library_id=mlib.id, kind="movie", title="Movie 900", guid="tmdb:900"
)
db.add_all([show500, movie900])
db.commit()
# A movie rec for id 500 must NOT match show s500; a tv rec for id 900 must NOT match movie m900.
miss = {
r["tmdb_id"]: r
for r in tmdb.resolve_recs(
db,
[
{"tmdb_id": 500, "title": "Movie 500", "media_type": "movie"},
{"tmdb_id": 900, "title": "Show 900", "media_type": "tv"},
],
)
}
assert miss[500]["in_library"] is False and miss[500]["rating_key"] is None
assert miss[900]["in_library"] is False and miss[900]["rating_key"] is None
# The right media_type DOES resolve to the right row.
hit = {
r["tmdb_id"]: r
for r in tmdb.resolve_recs(
db,
[
{"tmdb_id": 900, "media_type": "movie"},
{"tmdb_id": 500, "media_type": "tv"},
],
)
}
assert hit[900]["in_library"] is True and hit[900]["rating_key"] == "m900"
assert hit[500]["in_library"] is True and hit[500]["rating_key"] == "s500"