summaryrefslogtreecommitdiffstats
path: root/packages/meshbay-node/tests/test_media_meta_request.py
diff options
context:
space:
mode:
authorChristophe Besson <cbesson@gmail.com>2026-08-29 16:42:18 +0200
committerChristophe Besson <cbesson@gmail.com>2026-08-29 17:57:01 +0200
commit32318a64b12c83429c156a36c228769492e41d4e (patch)
tree87305a6977fabafa2f733044c4c0d776295626f3 /packages/meshbay-node/tests/test_media_meta_request.py
parent78eef92bfa3513a81ac64d4377f8300c533aaa6c (diff)
downloadmeshbay-32318a64b12c83429c156a36c228769492e41d4e.tar.gz
fix: on restart, serve a video's cached TMDB match instead of re-searching
Root of the 2026-08-29 demo35 storms. `_do_media_meta_request` could not tell "this is a movie" from "this video isn't enriched yet" — both have season/episode None — so during a slow initial scan with a browser on the Videos tab, every show episode requested was run through the *movie* search path with its raw filename as the query (`search/movie?query=Show S01E01 1080p WEB DL ...`), hundreds per second, until TMDB rate-limited and posters stopped loading. Worse, an un-enriched show episode's own valid cached "tv" match was treated as stale (its provisional kind was "movie"), so a file already resolved got re-queried anyway. - While a video is un-enriched (no display_title — enrich.py always sets one), never run a TMDB *search*. Serve the cached match if the content hash has one, honouring the cached kind ("tv"/"movie") rather than the provisional split; otherwise answer confidence 0. - Once enriched, the strict `cached_media_type == media_type` check is unchanged: an enrichment fix that reclassifies a folder movie->tv still drops the stale match and re-resolves. - video-app.js: `useMediaMeta` gains an `enrichSig` dependency (`entry.display_title`) so the client refetches once the enriched fields arrive on an index delta — the fileId is a content hash and never changes, so nothing else would retrigger it. Not caused by the V8-V13 work; it raised the per-file call count so the pre-existing race became a visible storm. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018BMLQjqFGCize2KtNBT79v
Diffstat (limited to 'packages/meshbay-node/tests/test_media_meta_request.py')
-rw-r--r--packages/meshbay-node/tests/test_media_meta_request.py61
1 files changed, 60 insertions, 1 deletions
diff --git a/packages/meshbay-node/tests/test_media_meta_request.py b/packages/meshbay-node/tests/test_media_meta_request.py
index a752825..0a3df12 100644
--- a/packages/meshbay-node/tests/test_media_meta_request.py
+++ b/packages/meshbay-node/tests/test_media_meta_request.py
@@ -9,7 +9,7 @@ real risk here too, since a season folder routinely holds many episodes.
import pytest
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey
-from meshbay_common.protocol import IndexEntry
+from meshbay_common.protocol import MNP, IndexEntry
from meshbay_node.indexer.group_index import GroupIndex
from meshbay_node.media_cache import MediaCache
from meshbay_node.transport.webrtc_server import WebRTCPeerSession
@@ -165,3 +165,62 @@ async def test_unknown_file_id_is_refused(media_cache):
await session._do_media_meta_request({"file_id": "nope"})
assert session.sent == [{"type": "error", "detail": "File not found"}]
+
+
+async def test_a_not_yet_enriched_entry_with_no_cache_is_answered_without_a_search(media_cache):
+ """
+ A video the indexer has seen but not enriched yet has no display_title
+ (enrich.py always sets one) and season/episode still None — which the
+ movie/show split reads as "movie" and hands its raw filename to TMDB's
+ movie search. During a slow initial scan with a browser on the Videos
+ tab that is a storm of `search/movie?query=<raw filename>` and bogus
+ cached matches (found live 2026-08-29). With nothing cached it must
+ answer confidence 0 and let the client refetch once the enriched fields
+ arrive — never guess a match from the raw filename.
+ """
+ index = GroupIndex(group_id="g" * 32, sk_node=Ed25519PrivateKey.generate())
+ entry = IndexEntry(id="id-raw", name="Show.S01E01.1080p.WEB.mkv",
+ path="shows/Show", size=1, type="video", added_at=0)
+ index.add_entry(entry)
+ client = FakeTmdbClient()
+ session = _session(index, media_cache, client)
+
+ await session._do_media_meta_request({"file_id": "id-raw"})
+
+ assert len(session.sent) == 1
+ resp = session.sent[0]
+ assert resp["type"] == MNP.MEDIA_META_RESP
+ assert resp["file_id"] == "id-raw"
+ assert resp["confidence"] == 0
+ assert "tmdb_id" not in resp
+ assert client.searched == [], "no TMDB search for a not-yet-enriched video"
+
+
+async def test_a_not_yet_enriched_entry_is_served_from_cache_without_a_search(media_cache):
+ """
+ The point the operator raised: a restart must not re-query TMDB for a
+ file already resolved. An un-enriched entry (season/episode not yet
+ populated) whose content hash already has a cached match is served
+ straight from that cache, honouring the cached *kind* rather than the
+ provisional "movie" the split would pick — so a show episode keeps its
+ real "tv" match instead of triggering a fresh movie search on its raw
+ filename.
+ """
+ index = GroupIndex(group_id="g" * 32, sk_node=Ed25519PrivateKey.generate())
+ entry = IndexEntry(id="id-known", name="Show.S02E05.1080p.WEB.mkv",
+ path="shows/Show", size=1, type="video", added_at=0)
+ index.add_entry(entry)
+ await media_cache.set_file_tmdb("id-known", "1396", "tv")
+ await media_cache.set_tmdb_meta("1396", "tv", {
+ "title": "The Cached Show", "original_title": "The Cached Show",
+ "first_air_date": "2008-01-20", "confidence": 1.0,
+ })
+ client = FakeTmdbClient()
+ session = _session(index, media_cache, client)
+
+ await session._do_media_meta_request({"file_id": "id-known"})
+
+ resp = session.sent[0]
+ assert resp["tmdb_id"] == "1396"
+ assert resp["title"] == "The Cached Show"
+ assert client.searched == [], "a cached match must not be re-searched on restart"