fix(playlists): overlapping writes, and what the second review found
A read from before a write no longer brings a row back: a write calls off the read on its way, what lands while one is out stays off the screen, and the last write out does the refresh. A failure puts back its own rows, an accepted reorder brings back none, and an Undo reads past the cache. A list the cache has dropped is read before a removal, so its Undo has places. undo-move refuses the same list twice, positions cannot repeat, and undo-move and the Plex key bodies refuse a body without their videos. Focus goes to the header's box when the last shown row or Save this order goes.
This commit is contained in:
@@ -437,6 +437,7 @@ def test_a_one_list_write_finds_its_list_under_the_lock(
|
||||
[
|
||||
("items/remove-bulk", {"ids": ["a"]}),
|
||||
("items/move-bulk", {"ids": ["a"], "target_playlist_id": 1}),
|
||||
("items/undo-move", {"ids": ["a"], "from_playlist_id": 1}),
|
||||
],
|
||||
)
|
||||
def test_a_body_without_its_videos_is_refused_rather_than_acting_on_none(
|
||||
@@ -448,6 +449,60 @@ def test_a_body_without_its_videos_is_refused_rather_than_acting_on_none(
|
||||
assert as_user(user).post(f"/api/playlists/{pl.id}/{path}", json=body).status_code == 422
|
||||
|
||||
|
||||
def test_an_undo_of_only_videos_the_target_held_names_no_moved_ones(db, make_user, as_user):
|
||||
"""An empty `moved` is a real body: every video of the move was already in the target."""
|
||||
user = make_user()
|
||||
_seed_videos(db, ["a"])
|
||||
src = _playlist(db, user, "Source", [])
|
||||
dst = _playlist(db, user, "Target", ["a"])
|
||||
|
||||
r = as_user(user).post(
|
||||
f"/api/playlists/{src.id}/items/undo-move",
|
||||
json={"from_playlist_id": dst.id, "moved": [], "already_there": ["a"], "positions": [0]},
|
||||
)
|
||||
|
||||
assert r.json()["added"] == 1
|
||||
assert _ids(db, src.id) == ["a"]
|
||||
assert _ids(db, dst.id) == ["a"]
|
||||
|
||||
|
||||
def test_an_undo_within_one_list_is_refused_and_keeps_its_videos(db, make_user, as_user):
|
||||
"""A move refuses the same list twice, so no Undo of one exists; a row must not go for asking."""
|
||||
user = make_user()
|
||||
_seed_videos(db, ["a", "b"])
|
||||
pl = _playlist(db, user, "L", ["a", "b"])
|
||||
|
||||
r = as_user(user).post(
|
||||
f"/api/playlists/{pl.id}/items/undo-move",
|
||||
json={"from_playlist_id": pl.id, "moved": ["a"], "positions": [0]},
|
||||
)
|
||||
|
||||
assert r.status_code == 400
|
||||
assert r.json()["detail"] == {"code": "playlist_move", "reason": "same_playlist"}
|
||||
assert _ids(db, pl.id) == ["a", "b"]
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("path", "key", "extra"),
|
||||
[
|
||||
("items/restore", "video_ids", {}),
|
||||
("items/undo-move", "moved", {"from_playlist_id": None}),
|
||||
],
|
||||
)
|
||||
def test_two_videos_cannot_ask_for_one_place(db, make_user, as_user, path, key, extra):
|
||||
user = make_user()
|
||||
_seed_videos(db, ["a", "b", "c"])
|
||||
pl = _playlist(db, user, "L", ["c"])
|
||||
other = _playlist(db, user, "Other", ["a", "b"])
|
||||
body = {key: ["a", "b"], "positions": [0, 0]}
|
||||
body.update({k: other.id for k in extra})
|
||||
|
||||
r = as_user(user).post(f"/api/playlists/{pl.id}/{path}", json=body)
|
||||
|
||||
assert r.status_code == 422
|
||||
assert _ids(db, pl.id) == ["c"]
|
||||
|
||||
|
||||
def test_a_reorder_without_its_videos_is_refused(db, make_user, as_user):
|
||||
user = make_user()
|
||||
pl = _playlist(db, user, "L", [])
|
||||
|
||||
@@ -6,6 +6,8 @@ rows, since positions have gaps after a removal. One request: an append plus a s
|
||||
leaves the rows at the end whenever that call fails, and would overwrite another tab's changes.
|
||||
"""
|
||||
|
||||
import pytest
|
||||
|
||||
from app.plex.models import PlexItem, PlexLibrary, PlexPlaylist, PlexPlaylistItem
|
||||
|
||||
|
||||
@@ -101,6 +103,22 @@ def test_positions_must_pair_with_the_keys(db, make_user, as_user):
|
||||
assert _order(db, pl.id)[0] == ["a"]
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("method", "path"),
|
||||
[("post", "items/bulk"), ("post", "items/remove-bulk"), ("put", "order")],
|
||||
)
|
||||
def test_a_body_without_its_keys_is_refused_rather_than_acting_on_none(
|
||||
db, make_user, as_user, method, path
|
||||
):
|
||||
user = make_user()
|
||||
pl = _seed(db, user, ["a"], extra=())
|
||||
|
||||
r = getattr(as_user(user), method)(f"/api/plex/playlists/{pl.id}/{path}", json={"keys": ["a"]})
|
||||
|
||||
assert r.status_code == 422
|
||||
assert _order(db, pl.id)[0] == ["a"]
|
||||
|
||||
|
||||
def test_remove_then_put_back_is_no_change(db, make_user, as_user):
|
||||
user = make_user()
|
||||
pl = _seed(db, user, ["a", "b", "c", "d"], extra=())
|
||||
|
||||
Reference in New Issue
Block a user