Files
jellyfin-scene-search/tests/test_multi_library.py
T
2026-10-06 17:59:01 +01:00

166 lines
5.3 KiB
Python

from __future__ import annotations
import tempfile
import unittest
from pathlib import Path
from types import SimpleNamespace
from unittest.mock import patch
from scene_search.cli import make_parser
from scene_search.database import connect, init_db
from scene_search.jellyfin_index import (
_reconcile_library,
resolve_libraries,
resolve_library_ids,
)
class FakeJellyfinClient:
def media_folders(self) -> list[dict]:
return [
{"Id": "lib-movies", "Name": "Movies"},
{"Id": "lib-tv", "Name": "TV"},
{"Id": "lib-home", "Name": "Home Videos"},
]
class MultiLibrarySelectionTests(unittest.TestCase):
def test_all_libraries_can_exclude_named_library(self) -> None:
client = FakeJellyfinClient()
with patch(
"scene_search.jellyfin_index.settings",
SimpleNamespace(
jellyfin_exclude_libraries=(),
jellyfin_libraries=(),
jellyfin_library="",
),
):
libraries = resolve_libraries(
client, # type: ignore[arg-type]
None,
all_libraries=True,
excluded=["TV"],
)
self.assertEqual(
[library["Name"] for library in libraries],
["Home Videos", "Movies"],
)
def test_repeated_library_arguments_select_multiple(self) -> None:
parser = make_parser()
args = parser.parse_args(
[
"jellyfin-index",
"--library",
"Movies",
"--library",
"TV",
]
)
self.assertEqual(args.library, ["Movies", "TV"])
self.assertFalse(args.all_libraries)
def test_plugin_library_ids_resolve_to_jellyfin_libraries(self) -> None:
client = FakeJellyfinClient()
libraries = resolve_library_ids(
client, # type: ignore[arg-type]
["lib-tv", "lib-movies"],
)
self.assertEqual(
[library["Name"] for library in libraries],
["Movies", "TV"],
)
def test_requested_library_names_are_case_insensitive(self) -> None:
client = FakeJellyfinClient()
with patch(
"scene_search.jellyfin_index.settings",
SimpleNamespace(
jellyfin_exclude_libraries=(),
jellyfin_libraries=(),
jellyfin_library="",
),
):
libraries = resolve_libraries(
client, # type: ignore[arg-type]
["movies", "HOME VIDEOS"],
)
self.assertEqual(
[library["Name"] for library in libraries],
["Home Videos", "Movies"],
)
class LibraryReconciliationTests(unittest.TestCase):
def test_reconcile_only_prunes_stale_items_from_target_library(self) -> None:
with tempfile.TemporaryDirectory() as tmp:
root = Path(tmp)
db_path = root / "scenes.sqlite3"
thumbs_dir = root / "thumbs"
init_db(db_path)
rows = [
("keep", "Keep", "jf-keep", "lib-a"),
("stale", "Stale", "jf-stale", "lib-a"),
("other", "Other", "jf-other", "lib-b"),
]
with connect(db_path) as con:
for media_id, title, jellyfin_id, library_id in rows:
con.execute(
"""
INSERT INTO media (
id, source_path, title, content_sha256, size_bytes,
mtime_ns, interval_ms, model_id, embedding_dim,
source_kind, jellyfin_item_id, jellyfin_library_id
)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
""",
(
media_id,
f"jellyfin://{jellyfin_id}",
title,
"hash",
0,
0,
10000,
"model",
2,
"jellyfin",
jellyfin_id,
library_id,
),
)
folder = thumbs_dir / media_id
folder.mkdir(parents=True)
(folder / "frame.jpg").write_bytes(b"x")
fake_settings = SimpleNamespace(
db_path=db_path,
thumbs_dir=thumbs_dir,
)
with patch("scene_search.jellyfin_index.settings", fake_settings):
pruned = _reconcile_library("lib-a", {"jf-keep"})
self.assertEqual(pruned, 1)
with connect(db_path) as con:
remaining = {
row["id"]
for row in con.execute("SELECT id FROM media").fetchall()
}
self.assertEqual(remaining, {"keep", "other"})
self.assertFalse((thumbs_dir / "stale").exists())
self.assertTrue((thumbs_dir / "keep").exists())
self.assertTrue((thumbs_dir / "other").exists())
if __name__ == "__main__":
unittest.main()