diff options
| author | Christophe Besson <cbesson@gmail.com> | 2026-09-26 14:28:13 +0200 |
|---|---|---|
| committer | Christophe Besson <cbesson@gmail.com> | 2026-09-26 14:28:13 +0200 |
| commit | eccd465f8954bfd49c3f7d8f02446bc2aa5c4338 (patch) | |
| tree | 0033f9232c56ca79c4838795c1cf5bdab13bc55f /packages/meshbay-node/tests/test_tmdb_language_gate.py | |
| parent | fc761e7df40eac828d4e9858fab56958078c928b (diff) | |
| download | meshbay-eccd465f8954bfd49c3f7d8f02446bc2aa5c4338.tar.gz | |
TMDB fiches are fetched lazily, on browse, and the fetch used to run whatever
the moment it was first triggered — routinely before the operator had opened
settings and picked a language, so it queried in TMDB's English default. Then
the fiche was cached by tmdb_id alone, with no note of language and a 30-day
TTL, so switching to the intended language afterwards changed nothing: the
English fiche was served until it expired. The operator's only recourse was to
find and wipe the cache by hand (found live 2026-09-26: a whole library indexed
in English although "Français" had been chosen).
Two rules now, both there to make the first fetch the right language rather
than English-then-corrected, and to stop the doubled requests that eventually
get a node rate-limited:
- No language configured, no query. media_meta_req/season_meta_req answer
confidence 0 and make no TMDB call while tmdb_language is unset; the fetch
waits for the operator's choice, so the first (and only) query is in it.
English is now a first-class choice (en-US), not the default of skipping the
setting.
- Changing the language wipes the metadata cache (ops.set_tmdb_config), so the
new language takes effect on an already-browsed library. The file->tmdb
matches are language-independent and kept. The client refetches on the
tmdb_config_ack that carries the new language, so the grid updates without a
page reload.
docs/MESHBAY_DESIGN.md §9.7 states both rules; tests cover the gate and the
cache wipe, and two existing handler harnesses now declare a language.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Diffstat (limited to 'packages/meshbay-node/tests/test_tmdb_language_gate.py')
| -rw-r--r-- | packages/meshbay-node/tests/test_tmdb_language_gate.py | 97 |
1 files changed, 97 insertions, 0 deletions
diff --git a/packages/meshbay-node/tests/test_tmdb_language_gate.py b/packages/meshbay-node/tests/test_tmdb_language_gate.py new file mode 100644 index 0000000..cd43fef --- /dev/null +++ b/packages/meshbay-node/tests/test_tmdb_language_gate.py @@ -0,0 +1,97 @@ +""" +The node does not query TMDB until a language is configured (§9.7). + +Browsing the Videos tab is what triggers the lazy `media_meta_req`, and it +routinely happens before the operator has opened the settings and chosen a +language. Querying then would fetch the whole library in TMDB's English default +and throw it away the moment a language was picked — double the requests against +TMDB's rate limit, for a result nobody asked for (found live 2026-09-26: a whole +library indexed in English before "Français" was chosen). So an unset language +answers confidence 0 and makes no call; a set language fetches once, in it. +""" + +import pytest +from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey +from meshbay_common.protocol import IndexEntry +from meshbay_node.indexer.group_index import GroupIndex +from meshbay_node.media_cache import MediaCache +from meshbay_node.transport.webrtc_server import WebRTCPeerSession + +pytestmark = pytest.mark.asyncio + + +class CountingTmdbClient: + def __init__(self): + self.calls = 0 + + async def search_movie(self, title, year=None): + self.calls += 1 + return {"id": 42, "title": title, "release_date": "2001-01-01"}, 1.0 + + async def search_tv(self, title, year=None): + self.calls += 1 + return {"id": 43, "name": title, "first_air_date": "2001-01-01"}, 1.0 + + async def movie_details(self, tmdb_id, language=None): + self.calls += 1 + return {"title": "A Film", "genres": [{"name": "Drama"}], + "poster_path": "/p.jpg", "overview": "x"} + + async def movie_credits(self, tmdb_id): + return {"cast": [], "crew": []} + + async def fetch_image(self, url): + return b"img" + + @staticmethod + def poster_url(path): + return f"https://image.tmdb.org/t/p/w500{path}" + + +@pytest.fixture +async def media_cache(tmp_path): + c = MediaCache(db_path=tmp_path / "media_cache.db") + await c.open() + yield c + await c.close() + + +def _session(media_cache, tmdb_client, language): + index = GroupIndex(group_id="g" * 32, sk_node=Ed25519PrivateKey.generate()) + entry = IndexEntry(id="f1", name="Some.Film.2001.mkv", path="movies", size=1, + type="video", added_at=0, display_title="Some Film") + index.add_entry(entry) + session = WebRTCPeerSession.__new__(WebRTCPeerSession) + session._ctx = { + "index": index, + "media_cache": media_cache, + "tmdb_client": tmdb_client, + "tmdb_enabled": True, + "daemon_state": {"tmdb_language": language}, + } + session._group_id = None + session.sent = [] + session._send = session.sent.append + return session, entry + + +async def test_no_language_makes_no_tmdb_call(media_cache): + client = CountingTmdbClient() + session, entry = _session(media_cache, client, language="") + + await session._do_media_meta_request({"file_id": entry.id}) + + assert client.calls == 0 + assert session.sent[-1]["confidence"] == 0 + # Nothing cached, so a later request in a real language still starts clean. + assert await media_cache.get_file_tmdb(entry.id) is None + + +async def test_a_configured_language_fetches(media_cache): + client = CountingTmdbClient() + session, entry = _session(media_cache, client, language="fr-FR") + + await session._do_media_meta_request({"file_id": entry.id}) + + assert client.calls > 0 + assert session.sent[-1].get("tmdb_id") == "42" |