Files
siftlode/backend/tests/test_scheduler.py
T
peter ab963c81d2 fix(plex,audit): E30 review round 1 — the people job keeps going, HU genres everywhere, results as data
- people_enrich: a failed TMDB lookup or a dead headshot is skipped, not the end of the run (a dead
  file is marked and left for a week; three failures in a row end it); portraits wait for the Plex
  module switch; a no-Plex original keeps its own cache key, so the resize still replaces it; the job
  entry lives in the plex domain (run_people_enrich)
- person-image takes only a headshot-shaped CDN url (no query variants of one file)
- every synced row gets Hungarian genres, and a HU genre filter matches the word as picked too;
  migration 0094 fills the rows no overlay reached
- a job run's audit row carries its result as data; audit_clear / demo_reset store their counts
- one credit-column list for the prune and the enrichment order; _overlay_pass typed
- tests: batch skip + stop, HU bio stored, dead headshots, module switch, the person-image route,
  the job body, genre filter + facet write + twin fallback, tagline/summary fallbacks, audit data,
  the reader's alphabet at every remaining collated() call site
2026-09-30 03:26:57 +02:00

89 lines
3.7 KiB
Python

"""_claim_running is the single-run guard that both the scheduled fire and the manual trigger go
through (at the top of _job): holding _activity_lock across the check-and-set means exactly one
caller can hold a job's "running" claim at a time, and it's claimable again once released."""
from sqlalchemy import select
from app import scheduler
from app.models import Notification
_JOB = "unit_test_claim" # not a real job id — exercises the primitive without touching live jobs
def _release():
scheduler._record(_JOB, running=False)
def test_claim_is_exclusive_until_released():
_release()
# First caller claims it.
assert scheduler._claim_running(_JOB) is True
# A second caller is refused while the first still holds it (the TOCTOU the fix closes).
assert scheduler._claim_running(_JOB) is False
assert scheduler._claim_running(_JOB) is False
# _job's finally releases the claim...
_release()
# ...and now it can be claimed again.
assert scheduler._claim_running(_JOB) is True
_release()
# ---- E30: the result as data -------------------------------------------------------------------
def _snapshot_job(job_id):
return next(j for j in scheduler.scheduler_snapshot()["jobs"] if j["id"] == job_id)
def test_a_run_keeps_its_result_as_data_for_the_readers_language(db, make_user):
"""The dashboard and the manual-run notice showed `_summarize`'s English one-liner ("new=3,
failed=0", a Python repr for a nested phase). The result rides along as data, so the client names
it; the one-liner stays as the fallback and the audit text."""
admin = make_user("sched@example.com", role="admin")
token = scheduler._manual_actor.set(admin.id)
try:
scheduler._job("rss_poll", lambda _db: {"new": 3, "failed": 0, "fallback": 1})
scheduler._job("enrich", lambda _db: 5) # enrich returns a bare count
finally:
scheduler._manual_actor.reset(token)
rss = _snapshot_job("rss_poll")
assert rss["result"] == {"new": 3, "failed": 0, "fallback": 1}
assert rss["last_result"] == "new=3, failed=0, fallback=1"
assert _snapshot_job("enrich")["result"] == {"enriched": 5}
notices = db.execute(
select(Notification).where(Notification.user_id == admin.id).order_by(Notification.id)
).scalars()
assert [n.data["result"] for n in notices] == [
{"new": 3, "failed": 0, "fallback": 1},
{"enriched": 5},
]
def test_a_failed_run_drops_the_previous_result(db):
scheduler._job("rss_poll", lambda _db: {"new": 1})
scheduler._job("rss_poll", lambda _db: (_ for _ in ()).throw(RuntimeError("boom")))
rss = _snapshot_job("rss_poll")
assert rss["last_error"] == "boom"
assert rss["result"] is None
def test_a_job_runs_audit_row_carries_its_result_as_data(db):
"""E30 review: the audit log names a run's result in the reader's language, as the inbox does —
from data, not from the English one-liner (a nested phase there was a Python dict repr)."""
from app.models import AuditLog, User
admin = User(email="auditrun@example.com")
db.add(admin)
db.commit()
scheduler._audit_run(db, None, "people_enrich", "ok", "x", {"bios": {"enriched": 2}})
scheduler._audit_run(db, admin.id, "rss_poll", "skipped", "sync paused", scheduler._PAUSED)
scheduler._audit_run(db, admin.id, "rss_poll", "error", "boom", None)
rows = db.execute(select(AuditLog).order_by(AuditLog.id)).scalars().all()
assert [r.after for r in rows] == [
{"status": "ok", "summary": "x", "result": {"bios": {"enriched": 2}}},
{"status": "skipped", "summary": "sync paused", "result": {"skipped": "sync paused"}},
{"status": "error", "summary": "boom"},
]