fix(plex): the watch reconcile settles each library on its own walk
A mirrored library that answers "0 items" is not believed (its rows stay), but that verdict stopped the whole reconcile: one library emptied on Plex with its mirrored items kept made every run incomplete, and un-watches in every other library never synced again. Each section now settles on its own walk, and only the settled sections' rows are cleared; the unsettled ones are named in the warning.
This commit is contained in:
@@ -79,6 +79,7 @@ def world(db):
|
||||
return {
|
||||
by_id[s.item_id]: (s.status, s.position_seconds, s.synced_to_plex)
|
||||
for s in db.execute(select(PlexState).where(PlexState.user_id == user.id)).scalars()
|
||||
if s.item_id in by_id # the five movies; a test's own extra items it reads itself
|
||||
}
|
||||
|
||||
return state, rows
|
||||
@@ -157,20 +158,40 @@ def test_a_genuinely_empty_second_section_still_lets_the_reconcile_clear(db, mon
|
||||
assert out["cleared"] == 1 and "incomplete" not in out
|
||||
|
||||
|
||||
def test_a_section_we_mirror_that_says_0_is_not_believed(db, monkeypatch, world):
|
||||
# A total of 0 from the very section our items come from is a Plex that lost the library for a
|
||||
# moment, not one that un-watched all of it (E32 review round 2): the shows section below says
|
||||
# 0 and lists nothing — while we hold an episode from it.
|
||||
state, rows = world
|
||||
_local_states(state)
|
||||
def _watched_episode_in_a_shows_section(db) -> int:
|
||||
"""A second mirrored section (key "2") holding one episode the owner has watched, synced."""
|
||||
shows = PlexLibrary(plex_key="2", title="Shows", kind="show", enabled=True)
|
||||
db.add(shows)
|
||||
db.flush()
|
||||
db.add(PlexItem(rating_key="e1", library_id=shows.id, kind="episode", title="e1"))
|
||||
ep = PlexItem(rating_key="e1", library_id=shows.id, kind="episode", title="e1")
|
||||
db.add(ep)
|
||||
db.flush()
|
||||
user_id = db.execute(select(PlexLink.user_id)).scalar_one()
|
||||
db.add(PlexState(user_id=user_id, item_id=ep.id, status="watched", synced_to_plex=True))
|
||||
return ep.id
|
||||
|
||||
|
||||
def _state_of(db, item_id: int) -> str | None:
|
||||
db.expire_all()
|
||||
st = db.execute(select(PlexState).where(PlexState.item_id == item_id)).scalar_one_or_none()
|
||||
return st.status if st else None
|
||||
|
||||
|
||||
def test_a_section_we_mirror_that_says_0_keeps_its_rows_and_does_not_hold_up_the_rest(
|
||||
db, monkeypatch, world
|
||||
):
|
||||
# A total of 0 from a section our items come from is a Plex that lost the library for a moment,
|
||||
# not one that un-watched all of it (E32 review round 2): its rows stay. And it is decided per
|
||||
# section: before round 3 one such section (a library emptied on Plex, its mirrored items kept)
|
||||
# made EVERY run incomplete, and un-watches in every other section never synced again.
|
||||
state, rows = world
|
||||
_local_states(state)
|
||||
ep_id = _watched_episode_in_a_shows_section(db)
|
||||
db.commit()
|
||||
out = _run(db, monkeypatch, _FakePlex(_LISTED, more=[("show", [], 0)]))
|
||||
assert rows()["m1"] == ("watched", 0, True)
|
||||
assert out["cleared"] == 0 and out["incomplete"] == 1
|
||||
assert _state_of(db, ep_id) == "watched"
|
||||
assert "m1" not in rows() # the movies section was walked whole: Plex un-watched m1
|
||||
assert out["cleared"] == 1 and out["incomplete"] == 1
|
||||
|
||||
|
||||
def test_a_library_the_allowlist_leaves_out_keeps_its_watch_states(db, monkeypatch, world):
|
||||
@@ -203,8 +224,8 @@ def test_a_library_the_allowlist_leaves_out_keeps_its_watch_states(db, monkeypat
|
||||
|
||||
|
||||
def test_a_cut_short_section_is_not_rescued_by_a_complete_one_after_it(db, monkeypatch, world):
|
||||
# Completeness is every section's, not the last one's: movies cut short (5 said, 3 handed),
|
||||
# then a complete shows listing — m1 may simply not have been reached.
|
||||
# Each section settles on its own walk: movies cut short (5 said, 3 handed), then a complete
|
||||
# shows listing — m1 may simply not have been reached.
|
||||
state, rows = world
|
||||
_local_states(state)
|
||||
db.commit()
|
||||
@@ -217,10 +238,12 @@ def test_a_cut_short_section_is_not_rescued_by_a_complete_one_after_it(db, monke
|
||||
def test_a_listing_without_a_total_is_never_complete(db, monkeypatch, world):
|
||||
state, rows = world
|
||||
_local_states(state)
|
||||
ep_id = _watched_episode_in_a_shows_section(db)
|
||||
db.commit()
|
||||
out = _run(db, monkeypatch, _FakePlex(_LISTED, more=[("show", [], None)]))
|
||||
assert rows()["m1"] == ("watched", 0, True)
|
||||
assert out["cleared"] == 0 and out["incomplete"] == 1
|
||||
assert _state_of(db, ep_id) == "watched" # its section never said how much it holds
|
||||
assert "m1" not in rows()
|
||||
assert out["cleared"] == 1 and out["incomplete"] == 1
|
||||
|
||||
|
||||
def test_an_empty_listing_is_not_a_library_of_unwatched_titles(db, monkeypatch, world):
|
||||
|
||||
Reference in New Issue
Block a user