feat(plex): add a movie or show to a collection from its card menu
"Add to collection…" opens a panel beside the card: the title's library's manual collections tick it in or out, and a name + Enter creates a new one with the title in it. It used to take the Collections page, a new collection, its name and a title search, then another search per title. A lean endpoint lists the collections with whether each holds the title. Admin only, as on the title's page. Also: a collection seeded with a show is made as a show collection (Plex type 2). Measured: as a movie one it came out empty and refused the show after.
This commit is contained in:
@@ -5,6 +5,7 @@ DB lane (`db` fixture) — skipped without Postgres.
|
||||
|
||||
from datetime import datetime, timezone
|
||||
|
||||
import pytest
|
||||
from fastapi.testclient import TestClient
|
||||
from sqlalchemy import select
|
||||
|
||||
@@ -224,8 +225,9 @@ class _CollStub:
|
||||
if self.fail_on == name:
|
||||
raise PlexError("boom")
|
||||
|
||||
def create_collection(self, section_key, title, item_rating_key):
|
||||
def create_collection(self, section_key, title, item_rating_key, item_type=1):
|
||||
self._maybe("create_collection")
|
||||
self.created_type = item_type
|
||||
return {"ratingKey": "NEW"}
|
||||
|
||||
def add_collection_item(self, rk, item):
|
||||
@@ -320,6 +322,22 @@ def test_create_writes_through_to_plex(db, monkeypatch, override_dep):
|
||||
assert body["editable"] is True # create marks the new collection editable
|
||||
|
||||
|
||||
@pytest.mark.parametrize(("seed", "plex_type"), [("s1", 2), ("m1", 1)])
|
||||
def test_create_makes_a_show_collection_as_one(db, monkeypatch, override_dep, seed, plex_type):
|
||||
# Measured on a real Plex: a collection made as a movie one (type 1) in a TV section came out
|
||||
# empty with a show seed, and refused the show when it was added after.
|
||||
lib, user = _seed_editable(db)
|
||||
db.add(PlexShow(rating_key="s1", library_id=lib.id, title="Series"))
|
||||
db.commit()
|
||||
stub = _CollStub()
|
||||
client = _admin_client(db, user, monkeypatch, override_dep, stub, resync=_resync_create)
|
||||
r = client.post(
|
||||
"/api/plex/collections", json={"library": "", "title": "New", "item_rating_key": seed}
|
||||
)
|
||||
assert r.status_code == 200, r.text
|
||||
assert stub.created_type == plex_type
|
||||
|
||||
|
||||
def test_create_resync_failure_is_422(db, monkeypatch, override_dep):
|
||||
# The collection was created in Plex (wrote=True) but the follow-up resync read failed → 422 with
|
||||
# the honest "reconcile on the next sync" detail, not a false "write failed".
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
"""A movie or show card's "Add to collection": the collections it can go into, each saying whether the
|
||||
title is in it already. DB lane (`db` fixture)."""
|
||||
|
||||
from datetime import datetime, timezone
|
||||
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
import app.state as state
|
||||
from app.auth import current_user
|
||||
from app.db import get_db
|
||||
from app.main import app
|
||||
from app.models import PlexCollection, PlexItem, PlexLibrary, PlexShow, User
|
||||
|
||||
|
||||
def _seed(db):
|
||||
movies = PlexLibrary(plex_key="1", title="Movies", kind="movie", enabled=True)
|
||||
shows = PlexLibrary(plex_key="2", title="Shows", kind="show", enabled=True)
|
||||
db.add_all([movies, shows])
|
||||
db.flush()
|
||||
enriched = datetime(2024, 1, 1, tzinfo=timezone.utc)
|
||||
db.add_all(
|
||||
[
|
||||
PlexItem(
|
||||
rating_key="m1",
|
||||
title="Film",
|
||||
kind="movie",
|
||||
library_id=movies.id,
|
||||
collection_keys=["cA"],
|
||||
people_enriched_at=enriched,
|
||||
),
|
||||
PlexItem(
|
||||
rating_key="e1",
|
||||
title="An episode",
|
||||
kind="episode",
|
||||
library_id=shows.id,
|
||||
people_enriched_at=enriched,
|
||||
),
|
||||
PlexShow(rating_key="s1", title="Series", library_id=shows.id, collection_keys=[]),
|
||||
# The movie library's: two manual ones it can go into, and three it cannot.
|
||||
PlexCollection(rating_key="cA", library_id=movies.id, title="Alpha", editable=True),
|
||||
PlexCollection(rating_key="cB", library_id=movies.id, title="Beta", editable=True),
|
||||
PlexCollection(rating_key="cP", library_id=movies.id, title="Plain", editable=False),
|
||||
PlexCollection(
|
||||
rating_key="cS", library_id=movies.id, title="Smart", editable=True, smart=True
|
||||
),
|
||||
PlexCollection(
|
||||
rating_key="cI", library_id=movies.id, title="IMDb Top 250", editable=True
|
||||
),
|
||||
# The show library's own.
|
||||
PlexCollection(rating_key="cT", library_id=shows.id, title="Sci-fi", editable=True),
|
||||
]
|
||||
)
|
||||
user = User(email="collfor@example.com")
|
||||
db.add(user)
|
||||
db.commit()
|
||||
return user
|
||||
|
||||
|
||||
def _get(db, monkeypatch, override_dep, rk):
|
||||
user = _seed(db)
|
||||
monkeypatch.setattr(state, "_configured_cache", True)
|
||||
override_dep(get_db, db.info["request"])
|
||||
override_dep(current_user, lambda: user)
|
||||
return TestClient(app).get(f"/api/plex/collections/for/{rk}")
|
||||
|
||||
|
||||
def test_a_movie_lists_its_librarys_manual_collections_and_which_hold_it(
|
||||
db, monkeypatch, override_dep
|
||||
):
|
||||
resp = _get(db, monkeypatch, override_dep, "m1")
|
||||
assert resp.status_code == 200
|
||||
got = [(c["id"], c["contains"]) for c in resp.json()["collections"]]
|
||||
assert got == [("cA", True), ("cB", False)]
|
||||
|
||||
|
||||
def test_a_show_lists_its_own_librarys(db, monkeypatch, override_dep):
|
||||
resp = _get(db, monkeypatch, override_dep, "s1")
|
||||
assert [(c["id"], c["contains"]) for c in resp.json()["collections"]] == [("cT", False)]
|
||||
|
||||
|
||||
def test_an_episode_or_an_unknown_key_is_not_a_collection_member(db, monkeypatch, override_dep):
|
||||
assert _get(db, monkeypatch, override_dep, "e1").status_code == 404
|
||||
Reference in New Issue
Block a user