feat(service-storage-harness): slice 3 — migrate MediaIndex onto harness (scoped replace_items, +service_id)

Register MediaIndex as a harness concern with ALTER TABLE migration to add
service_id column (idempotent). Scope replace_items by service_id (FIXES latent
global-clear bug where building for one Jellyfin wiped another's rows). Scope
query by service_id (empty-string = all rows, backward-compat). Thread
service_id through build_media_index + worker + query_media router. New
regression test proves scoped replace preserves other services' rows.

Backend: 321 pytest pass, ruff clean. Frontend: build green.
This commit is contained in:
Developer
2026-07-09 09:00:31 +00:00
parent 1fb12b8a0a
commit c87f398e37
5 changed files with 93 additions and 7 deletions
+47
View File
@@ -241,6 +241,53 @@ class TestMediaIndexReplace:
status = index.status()
assert status.item_count == 2
def test_replace_scoped_by_service_id_preserves_other_services(self, index):
"""Regression test: building for one Jellyfin must not wipe another's rows.
Before the migration, ``replace_items`` did ``DELETE FROM media_items``
(global clear). This test locks in the fix: a scoped replace preserves
rows belonging to a different service_id.
"""
rows_a = [
{"id": "a1", "title": "Alpha Movie", "type": "Movie", "library_id": "l1", "library_name": "L1"},
{"id": "a2", "title": "Alpha Show", "type": "Episode", "library_id": "l2", "library_name": "L2"},
]
rows_b = [
{"id": "b1", "title": "Beta Movie", "type": "Movie", "library_id": "l1", "library_name": "L1"},
]
index.replace_items(rows_a, service_id="svc-a")
assert index.status().item_count == 2
# Replacing for svc-b must NOT wipe svc-a's rows.
index.replace_items(rows_b, service_id="svc-b")
assert index.status().item_count == 3 # 2 from svc-a + 1 from svc-b
# Querying svc-a returns only its rows.
rows_a_result, total_a = index.query(
library_ids=["l1", "l2"],
media_types=["Movie", "Episode"],
service_id="svc-a",
)
assert total_a == 2
assert {r["id"] for r in rows_a_result} == {"a1", "a2"}
# Querying svc-b returns only its rows.
rows_b_result, total_b = index.query(
library_ids=["l1"],
media_types=["Movie"],
service_id="svc-b",
)
assert total_b == 1
assert rows_b_result[0]["id"] == "b1"
# Querying with no service_id returns all rows (backward-compat).
_, total_all = index.query(
library_ids=["l1", "l2"],
media_types=["Movie", "Episode"],
)
assert total_all == 3
class TestMediaIndexMetadata:
def test_set_and_read_metadata(self, index):