fix: every first watch state survives a write beside it, and a folded Watch later keeps its pin
A season marked watched, a pull from Plex and a YouTube resume point made their first row by a read then an add, and failed on the unique key when a first checkpoint or the watched mark made it in between. They go through the same insert-then-read as the checkpoint (the resume point is one upsert); a pull that meets a local change still waiting for its push leaves it. Folding duplicate Watch later lists moved their videos but not a Home pin, which the cascade then deleted; the first pin of a dropped copy moves onto the kept list. Marking one notice read trimmed before its own flag was flushed, so the trim left one past the cap. The route test now crosses the cap and reads the result from a session of its own.
This commit is contained in:
@@ -2,12 +2,14 @@
|
||||
|
||||
A route's helper only flushes; the handler commits. The `db` fixture fails a test whose request left
|
||||
a write uncommitted, so every test here drives one successful write through its route and then
|
||||
reads the result back — a handler that lost its commit fails on both counts. The services' own
|
||||
reads the result back. The read-back holds that the handler called its unit; it runs in the
|
||||
request's own session, so it sees a write that was never committed — the commit is the fixture's
|
||||
to hold (a test that must see it committed reads from a session of its own). The services' own
|
||||
tests hold what each write does; these hold that the route finishes it. Nothing here reaches
|
||||
YouTube or Plex: the YouTube client is a stub and no user has a Plex link. Needs the DB.
|
||||
"""
|
||||
|
||||
from datetime import datetime, timezone
|
||||
from datetime import datetime, timedelta, timezone
|
||||
|
||||
import pytest
|
||||
from sqlalchemy import select
|
||||
@@ -17,7 +19,9 @@ from app.auth import require_write_scope
|
||||
from app.catalog.models import Channel, ExploredChannel, SearchFind, Subscription, Video
|
||||
from app.downloads.models import DownloadJob, DownloadProfile, DownloadShare
|
||||
from app.plex.models import PlexItem, PlexLibrary, PlexSeason, PlexShow, PlexState
|
||||
from app.db import SessionLocal
|
||||
from app.models import Notification
|
||||
from app.notifications import READ_SOFT_CAP
|
||||
from app.reader.models import SavedView, Tag, VideoState
|
||||
from app.routes import channels
|
||||
|
||||
@@ -343,14 +347,37 @@ def test_a_season_marked_watched_through_the_route_stays_watched(db, make_user,
|
||||
assert _plex_marks(db, user) == [("e1", "watched"), ("e2", "watched")]
|
||||
|
||||
|
||||
def test_reading_one_notice_is_committed_with_its_trim(db, make_user, as_user):
|
||||
"""Marking a notice read and trimming the read ones past the cap are one write the handler
|
||||
commits — the trim no longer commits on its own."""
|
||||
user = make_user()
|
||||
n = Notification(user_id=user.id, type="t", title="x")
|
||||
db.add(n)
|
||||
def _read_past_the_cap(db, user) -> list[Notification]:
|
||||
"""READ_SOFT_CAP read notices, oldest first, and one unread notice after them."""
|
||||
base = datetime(2024, 1, 1, tzinfo=timezone.utc)
|
||||
rows = [
|
||||
Notification(
|
||||
user_id=user.id,
|
||||
type="t",
|
||||
title=f"n{i}",
|
||||
read=i < READ_SOFT_CAP,
|
||||
created_at=base + timedelta(minutes=i),
|
||||
)
|
||||
for i in range(READ_SOFT_CAP + 1)
|
||||
]
|
||||
db.add_all(rows)
|
||||
db.commit()
|
||||
r = as_user(user).post(f"/api/me/notifications/{n.id}/read")
|
||||
return rows
|
||||
|
||||
|
||||
def _committed_notices(user) -> tuple[int, bool]:
|
||||
"""What another session sees: how many of the user's notices are left, and whether the oldest is."""
|
||||
with SessionLocal() as s:
|
||||
ids = s.scalars(select(Notification.title).where(Notification.user_id == user.id)).all()
|
||||
return len(ids), "n0" in ids
|
||||
|
||||
|
||||
@pytest.mark.parametrize("path", ["{id}/read", "read_all"])
|
||||
def test_reading_a_notice_past_the_cap_commits_the_trim(db, make_user, as_user, path):
|
||||
"""Marking a notice read makes READ_SOFT_CAP + 1 read ones: the handler commits the read flag and
|
||||
the trim of the oldest together, and the trim counts the notice just read."""
|
||||
user = make_user()
|
||||
rows = _read_past_the_cap(db, user)
|
||||
r = as_user(user).post("/api/me/notifications/" + path.format(id=rows[-1].id))
|
||||
assert r.json() == {"ok": True}
|
||||
db.expire_all()
|
||||
assert db.get(Notification, n.id).read is True
|
||||
assert _committed_notices(user) == (READ_SOFT_CAP, False)
|
||||
|
||||
Reference in New Issue
Block a user