"""The library player's resume point (`library_states`). A checkpoint stores where the caller stopped in a library item they can play — their own download, or one shared with them — and the two library listings hand it back per item, so the player opens there. Too early or too close to the end clears it. Nobody else's checkpoint, and no item the caller cannot reach, is ever read or written. DB-backed (real Postgres). """ from sqlalchemy import select from app.models import DownloadJob, DownloadShare, LibraryState def _done_job(db, user) -> DownloadJob: job = DownloadJob( user_id=user.id, source_kind="url", source_ref="https://example.com/a.mp4", status="done", profile_snapshot={}, ) db.add(job) db.commit() return job def _checkpoint(client, job_id: int, position: float, duration: float = 3000) -> int: """Send a checkpoint the caller may make; the point the server kept (0 = cleared).""" r = client.post( f"/api/downloads/{job_id}/progress", json={"position_seconds": position, "duration_seconds": duration}, ) assert r.status_code == 200, r.text return r.json()["position_seconds"] def _states(db) -> list[tuple[int, int, int]]: db.expire_all() rows = db.execute( select(LibraryState.user_id, LibraryState.job_id, LibraryState.position_seconds) ).all() return [tuple(r) for r in rows] def test_a_checkpoint_is_listed_back_on_the_item(db, make_user, as_user): me = make_user() job = _done_job(db, me) c = as_user(me) r = c.post( f"/api/downloads/{job.id}/progress", json={"position_seconds": 754.9, "duration_seconds": 3000}, ) assert r.status_code == 200 and r.json() == { "ok": True, "job_id": job.id, "position_seconds": 754, } [item] = c.get("/api/downloads").json() assert item["position_seconds"] == 754 # A second checkpoint moves it rather than adding a row. assert _checkpoint(c, job.id, 900) == 900 assert _states(db) == [(me.id, job.id, 900)] def test_an_item_never_played_lists_zero(db, make_user, as_user): me = make_user() _done_job(db, me) [item] = as_user(me).get("/api/downloads").json() assert item["position_seconds"] == 0 def test_too_early_or_near_the_end_clears_it(db, make_user, as_user): me = make_user() job = _done_job(db, me) c = as_user(me) assert _checkpoint(c, job.id, 600) == 600 assert _checkpoint(c, job.id, 2995) == 0 assert _states(db) == [] assert _checkpoint(c, job.id, 600) == 600 assert _checkpoint(c, job.id, 3) == 0 assert _states(db) == [] # Clearing what is not there is not an error (the beacon and the last checkpoint of one stop). assert _checkpoint(c, job.id, 0) == 0 def test_a_recipient_keeps_their_own_place_in_a_shared_item(db, make_user, as_user): owner, friend = make_user(), make_user() job = _done_job(db, owner) db.add(DownloadShare(job_id=job.id, from_user_id=owner.id, to_user_id=friend.id)) db.commit() assert _checkpoint(as_user(owner), job.id, 100) == 100 assert _checkpoint(as_user(friend), job.id, 2000) == 2000 assert as_user(owner).get("/api/downloads").json()[0]["position_seconds"] == 100 assert as_user(friend).get("/api/downloads/shared").json()[0]["position_seconds"] == 2000 def test_an_item_the_caller_cannot_play_is_a_404_and_writes_nothing(db, make_user, as_user): owner, stranger = make_user(), make_user() job = _done_job(db, owner) r = as_user(stranger).post( f"/api/downloads/{job.id}/progress", json={"position_seconds": 600, "duration_seconds": 3000}, ) assert r.status_code == 404 assert _states(db) == [] def test_deleting_the_job_deletes_its_places(db, make_user, as_user): me = make_user() job = _done_job(db, me) assert _checkpoint(as_user(me), job.id, 600) == 600 db.delete(db.get(DownloadJob, job.id)) db.commit() assert _states(db) == []