"""DB-backed regression (E5 S5a): the HU-overlay sync writes Hungarian metadata onto the matched EN rows (paired by guid), never creating browsable HU rows, and overlay libraries are excluded from the browse scope. Also covers the canonical-guid parse and that the generated search_vector_hu populates. Uses the DB lane (`db` fixture) — skipped when no Postgres is reachable. Runs against the real schema (migrations 0063/0064) so the JSONB + STORED generated columns are exercised. """ from sqlalchemy import func, select from app.models import PlexItem, PlexLibrary from app.plex.sync import _canonical_guid, _sync_overlay from app.routes.plex.browse import _content_libs class _StubPlex: """Minimal PlexClient stand-in: _sync_overlay only calls section_items via _paginate.""" def __init__(self, items: list[dict]): self._items = items def section_items(self, key, item_type, start, size): return self._items[start : start + size], len(self._items) def _hu_movie(rk: str, title: str, summary: str, genres: list[str], guid: str | None) -> dict: meta = {"ratingKey": rk, "title": title, "summary": summary, "Genre": [{"tag": g} for g in genres]} if guid: meta["Guid"] = [{"id": guid}] return meta def test_canonical_guid_prefers_tmdb_then_imdb(): assert _canonical_guid({"Guid": [{"id": "imdb://tt1"}, {"id": "tmdb://55"}]}) == "tmdb:55" assert _canonical_guid({"Guid": [{"id": "imdb://tt9"}, {"id": "tvdb://7"}]}) == "imdb:tt9" assert _canonical_guid({"Guid": []}) is None assert _canonical_guid({}) is None def test_sync_overlay_writes_hu_onto_matched_en_rows(db): en_lib = PlexLibrary(plex_key="4", title="Movies", kind="movie") hu_lib = PlexLibrary(plex_key="9", title="Filmek", kind="movie", metadata_lang="hu") db.add_all([en_lib, hu_lib]) db.flush() hu_lib.overlay_of = en_lib.id matched = PlexItem( rating_key="m1", library_id=en_lib.id, kind="movie", title="Heat", guid="tmdb:1" ) unmatched = PlexItem( rating_key="m2", library_id=en_lib.id, kind="movie", title="Solo", guid="tmdb:2" ) db.add_all([matched, unmatched]) db.commit() stub = _StubPlex( [ _hu_movie("h1", "Öldöklés", "Egy rablás terve.", ["Akció", "Krimi"], "tmdb://1"), _hu_movie("h2", "Ismeretlen", "Nincs EN párja.", ["Dráma"], "tmdb://999"), ] ) stats = {"overlaid": 0} _sync_overlay(db, stub, hu_lib, en_lib.id, stats) db.commit() db.refresh(matched) db.refresh(unmatched) assert matched.title_hu == "Öldöklés" assert matched.summary_hu == "Egy rablás terve." assert matched.genres_hu == ["Akció", "Krimi"] assert matched.search_vector_hu is not None # the STORED generated column populated from title_hu # The unmatched EN row keeps NULL HU fields (serving falls back to EN). assert unmatched.title_hu is None assert stats["overlaid"] == 1 # No browsable rows were created — the HU items never became plex_items of the overlay library. hu_row_count = db.scalar( select(func.count(PlexItem.id)).where(PlexItem.library_id == hu_lib.id) ) assert hu_row_count == 0 # The EN library still holds exactly its two movies (no dup from the overlay pass). en_row_count = db.scalar( select(func.count(PlexItem.id)).where(PlexItem.library_id == en_lib.id) ) assert en_row_count == 2 def test_content_libs_excludes_overlay_libraries(db): en_lib = PlexLibrary(plex_key="4", title="Movies", kind="movie") en_shows = PlexLibrary(plex_key="1", title="TV Shows", kind="show") db.add_all([en_lib, en_shows]) db.flush() overlay = PlexLibrary( plex_key="9", title="Filmek", kind="movie", metadata_lang="hu", overlay_of=en_lib.id ) db.add(overlay) db.commit() movie_ids, show_ids = _content_libs(db) assert en_lib.id in movie_ids assert overlay.id not in movie_ids # the HU overlay is never browsable assert show_ids == [en_shows.id]