The grid served Hungarian titles, but every other card surface (Discover rails, the show page's episodes, search, collections, the person page, playlists, Home's resume rail, hero and comfort shelf) served English. The card serializers now take a required keyword-only lang (a forgotten call site fails instead of going English), and each of those reads takes ?lang=hu, with the language in its query key. A Hungarian search finds an episode by its Hungarian title too. An episode on a poster rail showed its 16:9 still cropped into a poster, titled only with the episode name; it now shows the show's poster and name, with S·E · title under it (option A). Plex's generic season names (Season 1, Specials) are rebuilt from the number in the reader's language; a season with a name of its own keeps it. Home's comfort-shelf move wrote its optimistic order to the key without the language, which nothing read any more; it writes the query's own key.
272 lines
10 KiB
Python
272 lines
10 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),
|
|
]
|
|
)
|
|
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"),
|
|
]
|
|
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"),
|
|
]
|