feat(reader): a Takeout video the catalog learns later is marked watched then; the import's status
A watched video the catalog did not know at import time waits in `takeout_items`. The Shorts job now settles those rows right after its probe (the only place a video's Short-ness becomes known), every user's, a commit each: `reader.takeout.apply_waiting`, its count in the job's result. `GET /api/takeout` is what the settings page reads: the user's last import run (`jobruns.last_run`) and the kept videos by kind and outcome, "waiting" for one not known yet.
This commit is contained in:
@@ -8,9 +8,10 @@ from datetime import datetime, timedelta, timezone
|
||||
import pytest
|
||||
from sqlalchemy import select
|
||||
|
||||
from app import scheduler
|
||||
from app import jobruns, scheduler
|
||||
from app.catalog.models import Channel, Video
|
||||
from app.models import Notification
|
||||
from app.models import JobRun, Notification
|
||||
from app.runstatus import RunStatus, RunTrigger
|
||||
from app.reader import service, takeout
|
||||
from app.reader.models import (
|
||||
Playlist,
|
||||
@@ -360,3 +361,83 @@ def test_an_ad_hoc_run_that_asks_for_it_tells_its_user_how_it_went(db, make_user
|
||||
assert [(n.data["job_id"], n.data["result"]) for n in notices] == [
|
||||
("takeout_import", {"watched": 3})
|
||||
]
|
||||
|
||||
|
||||
# --- the videos that wait ----------------------------------------------------------------------
|
||||
|
||||
|
||||
def test_waiting_videos_are_marked_once_the_shorts_probe_has_cleared_them(db, make_user):
|
||||
a, b = make_user("a@example.com"), make_user("b@example.com")
|
||||
_video(db, "later000000", probed=False)
|
||||
_video(db, "notyet00000", probed=False)
|
||||
db.commit()
|
||||
service.stage_takeout(
|
||||
db, a, TakeoutKind.WATCHED, {"later000000": (T0, None), "notyet00000": (T0, None)}
|
||||
)
|
||||
service.stage_takeout(db, b, TakeoutKind.WATCHED, {"later000000": (T0, None)})
|
||||
db.commit()
|
||||
assert takeout.apply_waiting(db) == {}
|
||||
|
||||
# The probe clears one of them; the other is still unprobed.
|
||||
db.get(Video, "later000000").shorts_probed = True
|
||||
db.commit()
|
||||
assert takeout.apply_waiting(db) == {"takeout_watched": 2}
|
||||
|
||||
assert _states(db, a)["later000000"].watched_at == T0
|
||||
assert _states(db, b)["later000000"].status == "watched"
|
||||
assert _rows(db, a)["notyet00000"].outcome is None
|
||||
assert takeout.apply_waiting(db) == {}
|
||||
|
||||
|
||||
def test_the_shorts_job_settles_the_waiting_rows_its_probe_made_ready(db, make_user):
|
||||
user = make_user("job-shorts@example.com")
|
||||
_video(db, "ready000000") # probed already: the job's probe has nothing left to do
|
||||
db.commit()
|
||||
service.stage_takeout(db, user, TakeoutKind.WATCHED, {"ready000000": (T0, None)})
|
||||
db.commit()
|
||||
|
||||
scheduler._shorts_job()
|
||||
|
||||
db.expire_all()
|
||||
run = db.execute(select(JobRun).where(JobRun.job == "shorts")).scalar_one()
|
||||
assert run.result == {"probed": 0, "shorts": 0, "takeout_watched": 1}
|
||||
assert _states(db, user)["ready000000"].status == "watched"
|
||||
|
||||
|
||||
# --- what the settings page reads --------------------------------------------------------------
|
||||
|
||||
|
||||
def test_the_status_is_the_users_own_last_run_and_counts(db, make_user, as_user):
|
||||
user, other = make_user("status@example.com"), make_user("other@example.com")
|
||||
assert as_user(user).get("/api/takeout").json() == {
|
||||
"run": None,
|
||||
"watched": {},
|
||||
"watch_later": {},
|
||||
}
|
||||
_video(db, "plain000000")
|
||||
_video(db, "short000000", short=True)
|
||||
db.commit()
|
||||
service.stage_takeout(
|
||||
db,
|
||||
user,
|
||||
TakeoutKind.WATCHED,
|
||||
{v: (T0, None) for v in ("plain000000", "short000000", "unknown0000")},
|
||||
)
|
||||
service.stage_takeout(db, user, TakeoutKind.WATCH_LATER, {"plain000000": (T0, None)})
|
||||
service.stage_takeout(db, other, TakeoutKind.WATCHED, {"plain000000": (T0, None)})
|
||||
service.apply_takeout_watched(db, user)
|
||||
db.commit()
|
||||
older = jobruns.start("takeout_import", RunTrigger.AD_HOC, user.id, f"user:{user.id}")
|
||||
jobruns.finish(older, RunStatus.OK, summary=None, result={"watched": 0})
|
||||
newest = jobruns.start("takeout_import", RunTrigger.AD_HOC, user.id, f"user:{user.id}")
|
||||
jobruns.finish(newest, RunStatus.OK, summary=None, result={"watched": 1})
|
||||
theirs = jobruns.start("takeout_import", RunTrigger.AD_HOC, other.id, f"user:{other.id}")
|
||||
jobruns.finish(theirs, RunStatus.OK, summary=None, result={"watched": 99})
|
||||
|
||||
body = as_user(user).get("/api/takeout").json()
|
||||
|
||||
assert body["run"]["status"] == "ok"
|
||||
assert body["run"]["result"] == {"watched": 1}
|
||||
assert body["run"]["finished_at"] is not None
|
||||
assert body["watched"] == {"watched": 1, "short": 1, "waiting": 1}
|
||||
assert body["watch_later"] == {"waiting": 1}
|
||||
|
||||
@@ -364,6 +364,8 @@ UNIT_COMMITS = {
|
||||
# The Takeout import is a recorded run with its own session: the watched part lands before
|
||||
# the Watch later part asks YouTube (which commits the videos it fetched).
|
||||
"reader/takeout.py:run_import",
|
||||
# Run inside the Shorts job, on its session: each user's waiting rows land on their own.
|
||||
"reader/takeout.py:apply_waiting",
|
||||
},
|
||||
# The catalog's ingest jobs commit per page or batch, a YouTube call between each: a page
|
||||
# fetched and stored is not lost when the next call fails, and an enrich that fails rolls back
|
||||
|
||||
Reference in New Issue
Block a user