From 2d144d76cee55cf8faaacf196e716a0930dfd7e9 Mon Sep 17 00:00:00 2001 From: Christophe Besson Date: Wed, 26 Aug 2026 01:08:58 +0200 Subject: fix(node,hub): key music/media metadata lookups by file_id, not path MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit IndexEntry.path is the *folder* a file is in (indexer.py's _virtual_dir docstring: "the directory a file appears in"), not the file itself. GroupIndex.get_entry_by_path() treated it as if it named one file, and every one of its four callers did too: _do_music_meta_request, _do_media_meta_request, _do_tmdb_override, and _admin_exec_tmdb_override. Any two files sharing a folder — an album is one folder with many tracks, a season is one folder with many episodes — collided: a lookup by path silently returned whichever entry the index happened to iterate to first, regardless of which file the client actually asked about. Found live (2026-08-25): three unrelated albums ("High Tone - Various", two "Le Peuple de l'Herbe" albums) all showed the same MusicBrainz cover, because all their representative tracks happened to sit in one "high_tone" folder alongside a track that legitimately matched that cover. A force-reload didn't help — the bug is server-side, not a stale client state. Fixed by keying these four request/response pairs by `file_id` (the entry's own content hash — already unique, already how every other lookup in the system identifies a file) instead of `path`, both in the wire messages (music_meta_req/resp, media_meta_req/resp, tmdb_override) and in music-app.js/video-app.js's own hooks. GroupIndex.get_entry_by_path is now unused and removed — GroupIndex.get_entry(file_id) already did the right thing. No test previously exercised either handler with two entries sharing a folder — the only existing coverage (test_tmdb_override_policy.py) gave each entry its own folder, so the bug never had a chance to show up. Added that scenario there and in two new test files, all confirmed failing against the pre-fix code before being confirmed green against the fix. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_013XSohfUQQiaE77qyFLgSv3 --- .../tests/test_tmdb_override_policy.py | 49 +++++++++++++++++----- 1 file changed, 38 insertions(+), 11 deletions(-) (limited to 'packages/meshbay-node/tests/test_tmdb_override_policy.py') diff --git a/packages/meshbay-node/tests/test_tmdb_override_policy.py b/packages/meshbay-node/tests/test_tmdb_override_policy.py index b8fa6f9..cd1cee1 100644 --- a/packages/meshbay-node/tests/test_tmdb_override_policy.py +++ b/packages/meshbay-node/tests/test_tmdb_override_policy.py @@ -65,7 +65,7 @@ def _entry(path: str, name: str, display_title: str) -> IndexEntry: # ── Refused before a challenge is even issued ─────────────────────────────── -async def test_missing_path_is_refused(tmp_path): +async def test_missing_file_id_is_refused(tmp_path): session = _session(tmp_path, "op", operator="op") session._has_admin_authority = lambda: True issued = [] @@ -79,24 +79,25 @@ async def test_missing_path_is_refused(tmp_path): async def test_missing_tmdb_id_is_refused(tmp_path): session = _session(tmp_path, "op", operator="op") - session._ctx["index"].add_entry(_entry("shared", "ep.mkv", "Show")) + entry = _entry("shared", "ep.mkv", "Show") + session._ctx["index"].add_entry(entry) session._has_admin_authority = lambda: True issued = [] session._issue_admin_challenge = lambda op, subject: issued.append((op, subject)) - session._do_tmdb_override({"path": "shared", "media_type": "tv"}) + session._do_tmdb_override({"file_id": entry.id, "media_type": "tv"}) assert not issued assert [m for m in session.sent if m.get("type") == "error"] -async def test_unknown_path_is_refused(tmp_path): +async def test_unknown_file_id_is_refused(tmp_path): session = _session(tmp_path, "op", operator="op") session._has_admin_authority = lambda: True issued = [] session._issue_admin_challenge = lambda op, subject: issued.append((op, subject)) - session._do_tmdb_override({"path": "nope", "tmdb_id": "123", "media_type": "tv"}) + session._do_tmdb_override({"file_id": "nope", "tmdb_id": "123", "media_type": "tv"}) assert not issued assert [m for m in session.sent if m.get("type") == "error"] @@ -104,25 +105,51 @@ async def test_unknown_path_is_refused(tmp_path): async def test_a_request_with_nobody_to_authorize_it_is_refused(tmp_path): session = _session(tmp_path, "member-1", operator="the-operator") - session._ctx["index"].add_entry(_entry("shared", "ep.mkv", "Show")) + entry = _entry("shared", "ep.mkv", "Show") + session._ctx["index"].add_entry(entry) session._has_admin_authority = lambda: False - session._do_tmdb_override({"path": "shared", "tmdb_id": "123", "media_type": "tv"}) + session._do_tmdb_override({"file_id": entry.id, "tmdb_id": "123", "media_type": "tv"}) assert [m for m in session.sent if m.get("type") == "error"] async def test_a_valid_request_is_signed(tmp_path): session = _session(tmp_path, "op", operator="op") - session._ctx["index"].add_entry(_entry("shared", "ep.mkv", "War of the Worlds")) + entry = _entry("shared", "ep.mkv", "War of the Worlds") + session._ctx["index"].add_entry(entry) session._has_admin_authority = lambda: True issued = [] session._issue_admin_challenge = lambda op, subject: issued.append((op, subject)) - session._do_tmdb_override({"path": "shared", "tmdb_id": "2255", "media_type": "tv"}) + session._do_tmdb_override({"file_id": entry.id, "tmdb_id": "2255", "media_type": "tv"}) assert issued == [(OP_TMDB_OVERRIDE, - "path=shared,tmdb_id=2255,media_type=tv")] + f"file_id={entry.id},tmdb_id=2255,media_type=tv")] + + +async def test_two_files_in_the_same_folder_are_told_apart(tmp_path): + """ + Regression (found live, 2026-08-25): `IndexEntry.path` is the *folder* a + file is in, not the file itself — two files in the same folder (any + multi-episode season) used to collide when looked up by path, silently + resolving to whichever entry the index happened to return first. Keyed + by `file_id` now, so two entries sharing a folder must resolve to their + own, distinct entries. + """ + session = _session(tmp_path, "op", operator="op") + e1 = _entry("shared/Season 1", "s01e01.mkv", "War of the Worlds") + e2 = _entry("shared/Season 1", "s01e02.mkv", "War of the Worlds") + session._ctx["index"].add_entry(e1) + session._ctx["index"].add_entry(e2) + session._has_admin_authority = lambda: True + issued = [] + session._issue_admin_challenge = lambda op, subject: issued.append((op, subject)) + + session._do_tmdb_override({"file_id": e2.id, "tmdb_id": "2255", "media_type": "tv"}) + + assert issued == [(OP_TMDB_OVERRIDE, + f"file_id={e2.id},tmdb_id=2255,media_type=tv")] # ── Applying the override ─────────────────────────────────────────────────── @@ -151,7 +178,7 @@ async def test_override_updates_every_entry_sharing_the_display_title(tmp_path): session._peer_registry = lambda: {"peer-1": peer} await session._admin_exec_tmdb_override( - {"subject": "path=shared/S1,tmdb_id=999,media_type=tv"}, + {"subject": f"file_id={s1.id},tmdb_id=999,media_type=tv"}, b"transcript", b"sig") for e in (s1, s2, s3): -- cgit v1.2.3