Found in the S1b UAT: 'Mert megnézted: Batman Forever' kept the English title, and the info page's TMDB recommendations kept TMDB's English titles even for titles we hold. A held recommendation is now named as the library names it, in the reader's language; one we lack keeps TMDB's title.
299 lines
11 KiB
Python
299 lines
11 KiB
Python
"""E5 S6: HU-overlay language serving. When ?lang=hu, the unified library, facets, genre filter and
|
|
detail endpoints serve the Hungarian `*_hu` columns (falling back to EN where a row has no HU twin);
|
|
?lang=en (the default) serves English. Also covers the imdb_rating IMDb-only / audience-score split.
|
|
DB lane (`db` fixture) — skipped without Postgres.
|
|
"""
|
|
|
|
from datetime import datetime, timezone
|
|
|
|
from fastapi.testclient import TestClient
|
|
|
|
import app.state as state
|
|
from app.auth import current_user
|
|
from app.db import get_db
|
|
from app.main import app
|
|
from app import sysconfig
|
|
from app.models import (
|
|
PlexCollection,
|
|
PlexItem,
|
|
PlexLibrary,
|
|
PlexPlaylist,
|
|
PlexPlaylistItem,
|
|
PlexSeason,
|
|
PlexShow,
|
|
PlexState,
|
|
User,
|
|
)
|
|
|
|
|
|
def _seed(db):
|
|
"""One EN movie library with a single movie that has both EN and HU metadata, plus a second movie
|
|
that is EN-only (no HU twin) to prove the coalesce fallback."""
|
|
lib = PlexLibrary(plex_key="1", title="Movies", kind="movie", enabled=True)
|
|
db.add(lib)
|
|
db.flush()
|
|
m1 = PlexItem(
|
|
rating_key="m1",
|
|
library_id=lib.id,
|
|
kind="movie",
|
|
title="English Title",
|
|
title_hu="Magyar Cím",
|
|
summary="English summary.",
|
|
summary_hu="Magyar összefoglaló.",
|
|
genres=["Drama"],
|
|
genres_hu=["Dráma", "Akció"],
|
|
tagline="English tagline.",
|
|
tagline_hu="Magyar mottó.",
|
|
rating=6.5,
|
|
imdb_rating=None,
|
|
year=2001,
|
|
added_at=datetime(2024, 1, 1, tzinfo=timezone.utc),
|
|
people_enriched_at=datetime(
|
|
2024, 1, 1, tzinfo=timezone.utc
|
|
), # serve from row, skip live enrich
|
|
)
|
|
m2 = PlexItem(
|
|
rating_key="m2",
|
|
library_id=lib.id,
|
|
kind="movie",
|
|
title="EN Only",
|
|
genres=["Comedy"], # no HU twin
|
|
year=2002,
|
|
added_at=datetime(2024, 1, 2, tzinfo=timezone.utc),
|
|
people_enriched_at=datetime(2024, 1, 2, tzinfo=timezone.utc),
|
|
)
|
|
db.add_all([m1, m2])
|
|
user = User(email="plexlang@example.com")
|
|
db.add(user)
|
|
db.commit()
|
|
return user
|
|
|
|
|
|
def _get(db, user, monkeypatch, override_dep, path):
|
|
monkeypatch.setattr(
|
|
state, "_configured_cache", True
|
|
) # the request gate 503s an unconfigured app
|
|
override_dep(get_db, db.info["request"])
|
|
override_dep(current_user, lambda: user)
|
|
client = TestClient(app) # no context manager: skip lifespan
|
|
return client.get(path)
|
|
|
|
|
|
def _titles(resp):
|
|
return {i["title"] for i in resp.json()["items"]}
|
|
|
|
|
|
def test_library_serves_active_language_titles(db, monkeypatch, override_dep):
|
|
user = _seed(db)
|
|
hu = _get(db, user, monkeypatch, override_dep, "/api/plex/library?scope=movie&lang=hu")
|
|
en = _get(db, user, monkeypatch, override_dep, "/api/plex/library?scope=movie&lang=en")
|
|
# HU serves the twin's Hungarian title; the EN-only row falls back to its English title.
|
|
assert _titles(hu) == {"Magyar Cím", "EN Only"}
|
|
assert _titles(en) == {"English Title", "EN Only"}
|
|
|
|
|
|
def test_facets_vocabulary_is_language_specific(db, monkeypatch, override_dep):
|
|
user = _seed(db)
|
|
hu = _get(db, user, monkeypatch, override_dep, "/api/plex/facets?scope=movie&lang=hu")
|
|
en = _get(db, user, monkeypatch, override_dep, "/api/plex/facets?scope=movie&lang=en")
|
|
hu_genres = {g["value"] for g in hu.json()["genres"]}
|
|
en_genres = {g["value"] for g in en.json()["genres"]}
|
|
assert "Dráma" in hu_genres and "Drama" not in hu_genres
|
|
assert "Drama" in en_genres and "Dráma" not in en_genres
|
|
|
|
|
|
def test_genre_filter_matches_active_language_vocabulary(db, monkeypatch, override_dep):
|
|
user = _seed(db)
|
|
# The HU genre chip filters the HU column; the same string against the EN column matches nothing.
|
|
hit = _get(
|
|
db, user, monkeypatch, override_dep, "/api/plex/library?scope=movie&lang=hu&genres=Dráma"
|
|
)
|
|
miss = _get(
|
|
db, user, monkeypatch, override_dep, "/api/plex/library?scope=movie&lang=en&genres=Dráma"
|
|
)
|
|
assert _titles(hit) == {"Magyar Cím"}
|
|
assert miss.json()["items"] == []
|
|
|
|
|
|
def test_a_genre_filter_saved_in_english_still_matches_in_hungarian(db, monkeypatch, override_dep):
|
|
"""E30: the HU column holds one Hungarian word per genre. A filter saved while the HU list still
|
|
offered "Action" is read through the same map, so it finds the "Akció" title instead of nothing."""
|
|
user = _seed(db)
|
|
resp = _get(
|
|
db, user, monkeypatch, override_dep, "/api/plex/library?scope=movie&lang=hu&genres=Action"
|
|
)
|
|
assert _titles(resp) == {"Magyar Cím"}
|
|
|
|
|
|
def test_item_detail_language_and_rating_split(db, monkeypatch, override_dep):
|
|
user = _seed(db)
|
|
resp = _get(db, user, monkeypatch, override_dep, "/api/plex/item/m1?lang=hu")
|
|
body = resp.json()
|
|
assert body["title"] == "Magyar Cím"
|
|
assert body["summary"] == "Magyar összefoglaló."
|
|
assert body["tagline"] == "Magyar mottó." # E30: the HU overlay's tagline
|
|
en = _get(db, user, monkeypatch, override_dep, "/api/plex/item/m1").json()
|
|
assert en["tagline"] == "English tagline."
|
|
# imdb_rating is IMDb-only (None here); the audience score is served separately as `rating`.
|
|
assert body["imdb_rating"] is None
|
|
assert body["rating"] == 6.5
|
|
|
|
|
|
def test_hu_search_still_finds_en_only_titles(db, monkeypatch, override_dep):
|
|
user = _seed(db)
|
|
# m2 ("EN Only") has no HU twin, so search_vector_hu carries no title tokens for it. HU-mode search
|
|
# must still match it via the EN vector (vector_match's union) — findable by its English title.
|
|
resp = _get(db, user, monkeypatch, override_dep, "/api/plex/library?scope=movie&lang=hu&q=Only")
|
|
assert "EN Only" in _titles(resp)
|
|
|
|
|
|
def test_rating_filter_prefers_imdb_over_audience(db, monkeypatch, override_dep):
|
|
lib = PlexLibrary(plex_key="1", title="Movies", kind="movie", enabled=True)
|
|
db.add(lib)
|
|
db.flush()
|
|
# imdb 8.0 but audience 4.0 → a rating_min of 7 must MATCH (filter the displayed best score,
|
|
# IMDb preferred), where filtering the raw audience column (4.0) would wrongly exclude it.
|
|
db.add(
|
|
PlexItem(
|
|
rating_key="r1",
|
|
library_id=lib.id,
|
|
kind="movie",
|
|
title="High IMDb",
|
|
imdb_rating=8.0,
|
|
rating=4.0,
|
|
year=2001,
|
|
added_at=datetime(2024, 1, 1, tzinfo=timezone.utc),
|
|
people_enriched_at=datetime(2024, 1, 1, tzinfo=timezone.utc),
|
|
)
|
|
)
|
|
user = User(email="rating@example.com")
|
|
db.add(user)
|
|
db.commit()
|
|
hit = _get(db, user, monkeypatch, override_dep, "/api/plex/library?scope=movie&rating_min=7")
|
|
assert "High IMDb" in _titles(hit)
|
|
miss = _get(db, user, monkeypatch, override_dep, "/api/plex/library?scope=movie&rating_min=9")
|
|
assert "High IMDb" not in _titles(miss)
|
|
|
|
|
|
def _seed_rails(db):
|
|
"""`_seed` plus what every card rail reads: a HU-overlaid show with a HU-overlaid episode in
|
|
progress, a cast name, a collection, and a playlist holding the movie and the episode."""
|
|
user = _seed(db)
|
|
m1 = db.query(PlexItem).filter_by(rating_key="m1").one()
|
|
m1.cast_names = ["Anna"]
|
|
m1.collection_keys = ["c1"]
|
|
db.add(PlexCollection(rating_key="c1", library_id=m1.library_id, title="Coll"))
|
|
tv = PlexLibrary(plex_key="2", title="TV Shows", kind="show", enabled=True)
|
|
db.add(tv)
|
|
db.flush()
|
|
show = PlexShow(
|
|
rating_key="s1",
|
|
library_id=tv.id,
|
|
title="English Show",
|
|
title_hu="Magyar Sorozat",
|
|
added_at=datetime(2024, 1, 3, tzinfo=timezone.utc),
|
|
)
|
|
db.add(show)
|
|
db.flush()
|
|
season = PlexSeason(rating_key="s1s1", show_id=show.id, title="Season 1", season_number=1)
|
|
db.add(season)
|
|
db.flush()
|
|
ep = PlexItem(
|
|
rating_key="e1",
|
|
library_id=tv.id,
|
|
kind="episode",
|
|
show_id=show.id,
|
|
season_id=season.id,
|
|
title="Pilot",
|
|
title_hu="Kezdet",
|
|
season_number=1,
|
|
episode_number=1,
|
|
duration_s=3000,
|
|
)
|
|
db.add(ep)
|
|
db.flush()
|
|
db.add(
|
|
PlexState(
|
|
user_id=user.id,
|
|
item_id=ep.id,
|
|
status="in_progress",
|
|
position_seconds=600,
|
|
progress_updated_at=datetime(2024, 2, 1, tzinfo=timezone.utc),
|
|
)
|
|
)
|
|
pl = PlexPlaylist(user_id=user.id, title="Mix")
|
|
db.add(pl)
|
|
db.flush()
|
|
db.add_all(
|
|
[
|
|
PlexPlaylistItem(playlist_id=pl.id, item_id=m1.id, position=0),
|
|
PlexPlaylistItem(playlist_id=pl.id, item_id=ep.id, position=1),
|
|
]
|
|
)
|
|
# TMDB extras: m2 recommends m1 (held) and a title we lack; m1, watched, anchors the recs rail.
|
|
m1.guid = "tmdb:1"
|
|
m1.tmdb_enriched_at = datetime(2024, 1, 1, tzinfo=timezone.utc)
|
|
m1.recommendations = [{"tmdb_id": 2, "title": "EN Only", "media_type": "movie"}]
|
|
m2 = db.query(PlexItem).filter_by(rating_key="m2").one()
|
|
m2.guid = "tmdb:2"
|
|
m2.tmdb_enriched_at = datetime(2024, 1, 1, tzinfo=timezone.utc)
|
|
m2.recommendations = [
|
|
{"tmdb_id": 1, "title": "English Title", "media_type": "movie"},
|
|
{"tmdb_id": 77, "title": "Elsewhere", "media_type": "movie"},
|
|
]
|
|
db.add(
|
|
PlexState(
|
|
user_id=user.id,
|
|
item_id=m1.id,
|
|
status="watched",
|
|
watched_at=datetime(2024, 3, 1, tzinfo=timezone.utc),
|
|
)
|
|
)
|
|
sysconfig.set_value(db, "plex_enabled", True)
|
|
db.commit()
|
|
return user, pl.id
|
|
|
|
|
|
def test_every_card_rail_serves_the_readers_language(db, monkeypatch, override_dep):
|
|
"""E30: the grid spoke Hungarian, but every other card surface (Discover rails, show page,
|
|
search, collection, person page, playlist, Home) served the English title to a HU reader."""
|
|
user, pid = _seed_rails(db)
|
|
|
|
def get(path):
|
|
return _get(db, user, monkeypatch, override_dep, path).json()
|
|
|
|
def titles(cards):
|
|
return {c["title"] for c in cards}
|
|
|
|
rails = get("/api/plex/discover?scope=both&lang=hu")
|
|
assert {"Magyar Cím", "Magyar Sorozat"} <= titles(rails["recently_added"])
|
|
assert "Magyar Sorozat" in titles(rails["continue_watching"])
|
|
assert titles(get("/api/plex/show/s1?lang=hu")["seasons"][0]["episodes"]) == {"Kezdet"}
|
|
found = get("/api/plex/library?scope=both&lang=hu&q=Kezdet")["episodes"]
|
|
assert [(e["title"], e["show_title"]) for e in found] == [("Kezdet", "Magyar Sorozat")]
|
|
assert titles(get("/api/plex/collections/c1?lang=hu")["items"]) == {"Magyar Cím"}
|
|
assert titles(get("/api/plex/person/Anna?lang=hu")["movies"]) == {"Magyar Cím"}
|
|
items = get(f"/api/plex/playlists/{pid}?lang=hu")["items"]
|
|
assert [(c["title"], c.get("show_title")) for c in items] == [
|
|
("Magyar Cím", None),
|
|
("Kezdet", "Magyar Sorozat"),
|
|
]
|
|
# A recommendation we hold is named as the library names it; the rail's anchor too.
|
|
recs = get("/api/plex/item/m2?lang=hu")["tmdb"]["recommendations"]
|
|
assert [(r["title"], r["in_library"]) for r in recs] == [
|
|
("Magyar Cím", True),
|
|
("Elsewhere", False),
|
|
]
|
|
byw = get("/api/plex/discover?scope=both&lang=hu")["because_you_watched"]
|
|
assert byw["anchor"]["title"] == "Magyar Cím"
|
|
home = get("/api/discover?lang=hu")["continue_watching"]
|
|
plex = [c for c in home if c["source"] == "plex"]
|
|
assert [c["title"] for c in plex] == ["Magyar Sorozat"]
|
|
assert plex[0]["next"]["title"] == "Kezdet"
|
|
# EN (the default) still serves English on the same rails.
|
|
items = get(f"/api/plex/playlists/{pid}")["items"]
|
|
assert [(c["title"], c.get("show_title")) for c in items] == [
|
|
("English Title", None),
|
|
("Pilot", "English Show"),
|
|
]
|