From 6af05abf410bbd038ce7fa6915a659defc509071 Mon Sep 17 00:00:00 2001 From: Christophe Besson Date: Mon, 24 Aug 2026 10:04:46 +0200 Subject: feat(node,hub): add Videos group app (poster grid, flat list, TMDB metadata) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Implements docs/mediacenter.md: a "Videos" group application built on the existing files index rather than a separate catalogue. On the node side, new indexer enrichment (technical probe, filename/season parsing, thumbnail generation) runs per-file once an operator has chosen a video_root for the group, plus a TMDB client for on-demand poster/metadata lookups (never client-side, thumbnails delivered over the existing chunk path). On the hub side, a new video-app.js renders a lazily-mounted poster grid or a thumbnail-only flat list, with TMDB entirely optional per group. Along the way: the global apps registry now drives Settings' default-tab picker instead of a hardcoded list, and the video_root is configured from group Settings (like uploads) rather than from Files, with the node refusing to run any TMDB/thumbnail work until one is set. Fixes several bugs found via live testing against a real library, notably a race between two effects writing the same "image ready" state that could leave a poster grid spinning forever on a same-tab revisit — see mediacenter.md §5.4 for the full account of each one. --- packages/meshbay-node/tests/test_media_cache.py | 71 +++++++++++++++++++++++++ 1 file changed, 71 insertions(+) create mode 100644 packages/meshbay-node/tests/test_media_cache.py (limited to 'packages/meshbay-node/tests/test_media_cache.py') diff --git a/packages/meshbay-node/tests/test_media_cache.py b/packages/meshbay-node/tests/test_media_cache.py new file mode 100644 index 0000000..b66c448 --- /dev/null +++ b/packages/meshbay-node/tests/test_media_cache.py @@ -0,0 +1,71 @@ +"""Tests for media_cache.py — TMDB/thumbnail cache and its pruning obligation.""" + +import time + +import pytest + +from meshbay_node.media_cache import MediaCache, TMDB_META_TTL_SECS + + +@pytest.fixture +async def cache(tmp_path): + c = MediaCache(db_path=tmp_path / "media_cache.db") + await c.open() + yield c + await c.close() + + +@pytest.mark.asyncio +async def test_file_tmdb_round_trip(cache): + assert await cache.get_file_tmdb("file1") is None + + await cache.set_file_tmdb("file1", "12345", "movie") + + assert await cache.get_file_tmdb("file1") == ("12345", "movie") + + +@pytest.mark.asyncio +async def test_tmdb_meta_round_trip(cache): + assert await cache.get_tmdb_meta("12345", "movie") is None + + await cache.set_tmdb_meta("12345", "movie", {"title": "A Movie", "vote_average": 7.5}) + + meta = await cache.get_tmdb_meta("12345", "movie") + assert meta == {"title": "A Movie", "vote_average": 7.5} + + +@pytest.mark.asyncio +async def test_tmdb_meta_expires_after_ttl(cache): + await cache._db.execute( + "INSERT INTO tmdb_meta (tmdb_id, media_type, json, fetched_at) VALUES (?, ?, ?, ?)", + ("999", "movie", '{"title": "Old"}', time.time() - TMDB_META_TTL_SECS - 1), + ) + await cache._db.commit() + + assert await cache.get_tmdb_meta("999", "movie") is None + + +@pytest.mark.asyncio +async def test_thumb_round_trip(cache): + assert await cache.get_thumb("thumbhash1") is None + + await cache.put_thumb("thumbhash1", "file1", b"\xff\xd8fakejpeg") + + assert await cache.get_thumb("thumbhash1") == b"\xff\xd8fakejpeg" + + +@pytest.mark.asyncio +async def test_prune_file_removes_thumb_and_mapping_but_not_shared_meta(cache): + # Two episodes of the same show share one tmdb_meta row (§2's stated case). + await cache.set_file_tmdb("ep1", "555", "tv") + await cache.set_file_tmdb("ep2", "555", "tv") + await cache.set_tmdb_meta("555", "tv", {"name": "A Show"}) + await cache.put_thumb("thumb-ep1", "ep1", b"jpeg-bytes-1") + + await cache.prune_file("ep1") + + assert await cache.get_file_tmdb("ep1") is None + assert await cache.get_thumb("thumb-ep1") is None + # ep2's own mapping and the shared show metadata both survive + assert await cache.get_file_tmdb("ep2") == ("555", "tv") + assert await cache.get_tmdb_meta("555", "tv") == {"name": "A Show"} -- cgit v1.2.3