fix(activity): an ad-hoc run's row is one run's, a rollback keeps a run whole, Back reads one entry's filters
- The channel-backfill row says "running" from the runs this process has open, not from the table: a row whose outcome failed to write no longer reads as running until a restart. The run still going leads the row, so a newer run's outcome never sits under the older one's channel. - Migration 0109's downgrade carries every run's trigger, spend and start in the audit row, and the upgrade restores them: a manual run whose admin was deleted stays manual. - Back/Forward is read by the page's one popstate listener, both halves in one render; the server log adopts its half during that render, so no read goes out with one entry's filters and the other's range. - The server log's failed facets use the other tabs' words (the menus may be short); the accounts message shows only when the menu really lacks them.
This commit is contained in:
@@ -3,6 +3,7 @@ outcome intact, and leave the audit log; an admin action stays where it was. The
|
||||
them back."""
|
||||
|
||||
import json
|
||||
from datetime import timedelta
|
||||
|
||||
from alembic import command
|
||||
from alembic.config import Config
|
||||
@@ -87,14 +88,28 @@ def test_the_audit_logs_runs_move_into_the_run_table_and_back(db, make_user):
|
||||
).all()
|
||||
finally:
|
||||
command.upgrade(cfg, "head")
|
||||
|
||||
# What the audit row has no column for rides along, so the next upgrade restores it.
|
||||
def run(trigger: str) -> dict:
|
||||
return {"trigger": trigger, "quota_units": 0, "started_at": "2026-10-01T03:00:00+00:00"}
|
||||
|
||||
assert [tuple(r) for r in back] == [
|
||||
(None, "rss_poll", {"status": "ok", "summary": "new=3", "result": {"new": 3}}),
|
||||
(
|
||||
None,
|
||||
"rss_poll",
|
||||
{"status": "ok", "summary": "new=3", "result": {"new": 3}, **run("scheduled")},
|
||||
),
|
||||
(
|
||||
uid,
|
||||
"rss_poll",
|
||||
{"status": "error", "summary": "boom", "result": {"failure": "rss_all_failed"}},
|
||||
{
|
||||
"status": "error",
|
||||
"summary": "boom",
|
||||
"result": {"failure": "rss_all_failed"},
|
||||
**run("manual"),
|
||||
},
|
||||
),
|
||||
(uid, "backfill", {"status": "skipped", "summary": "sync paused"}),
|
||||
(uid, "backfill", {"status": "skipped", "summary": "sync paused", **run("manual")}),
|
||||
]
|
||||
|
||||
|
||||
@@ -106,10 +121,13 @@ def test_a_down_and_up_round_trip_keeps_every_run(db, make_user):
|
||||
conn.execute(
|
||||
text(
|
||||
"INSERT INTO job_runs (job, trigger, actor_id, target, status, started_at,"
|
||||
" finished_at) VALUES"
|
||||
" ('backfill', 'scheduled', NULL, NULL, 'interrupted', now(), now()),"
|
||||
" ('maintenance', 'manual', :u, NULL, 'running', now(), NULL),"
|
||||
" ('channel_backfill', 'ad_hoc', :u, 'UC_x', 'ok', now(), now())"
|
||||
" finished_at, quota_units) VALUES"
|
||||
" ('backfill', 'scheduled', NULL, NULL, 'interrupted', now(), now(), 0),"
|
||||
" ('maintenance', 'manual', :u, NULL, 'running', now(), NULL, 0),"
|
||||
" ('channel_backfill', 'ad_hoc', :u, 'UC_x', 'ok', now(), now(), 0),"
|
||||
# Its admin is gone (SET NULL): only the row itself still says it was manual.
|
||||
" ('enrich', 'manual', NULL, NULL, 'ok',"
|
||||
" '2026-10-01 10:00+00', '2026-10-01 10:30+00', 42)"
|
||||
),
|
||||
{"u": uid},
|
||||
)
|
||||
@@ -120,6 +138,9 @@ def test_a_down_and_up_round_trip_keeps_every_run(db, make_user):
|
||||
runs = conn.execute(
|
||||
text("SELECT job, trigger, actor_id, target, status FROM job_runs ORDER BY id")
|
||||
).all()
|
||||
kept = conn.execute(
|
||||
text("SELECT quota_units, finished_at - started_at FROM job_runs WHERE job = 'enrich'")
|
||||
).one()
|
||||
stranded = conn.execute(
|
||||
text("SELECT count(*) FROM audit_log WHERE action = 'job_run'")
|
||||
).scalar()
|
||||
@@ -127,5 +148,7 @@ def test_a_down_and_up_round_trip_keeps_every_run(db, make_user):
|
||||
("backfill", "scheduled", None, None, "interrupted"),
|
||||
("maintenance", "manual", uid, None, "interrupted"), # the rollback stopped it
|
||||
("channel_backfill", "ad_hoc", uid, "UC_x", "ok"),
|
||||
("enrich", "manual", None, None, "ok"),
|
||||
]
|
||||
assert tuple(kept) == (42, timedelta(minutes=30))
|
||||
assert stranded == 0
|
||||
|
||||
@@ -363,8 +363,15 @@ def test_the_page_shows_the_ad_hoc_runs_beside_the_jobs_with_their_channel(db, m
|
||||
assert len(row["recent"]) == 2
|
||||
assert all(j["id"] != "channel_backfill" for j in scheduler.scheduler_snapshot(db)["jobs"])
|
||||
|
||||
jobruns.start("channel_backfill", RunTrigger.AD_HOC, user.id, "UC_shown")
|
||||
assert scheduler.scheduler_snapshot(db)["ad_hoc"][0]["running"] is True
|
||||
seen = []
|
||||
scheduler._ad_hoc(
|
||||
"channel_backfill",
|
||||
lambda: seen.append(scheduler.scheduler_snapshot(db)["ad_hoc"][0]["running"]) or {},
|
||||
user.id,
|
||||
"UC_shown",
|
||||
)
|
||||
assert seen == [True]
|
||||
assert scheduler.scheduler_snapshot(db)["ad_hoc"][0]["running"] is False
|
||||
|
||||
|
||||
def _finished(db, job) -> list[JobRun]:
|
||||
@@ -458,6 +465,8 @@ def test_a_run_whose_outcome_cannot_be_written_is_not_called_failed(db, make_use
|
||||
calls.clear()
|
||||
scheduler._ad_hoc("channel_backfill", lambda: {"videos_added": 1}, user.id, "UC_blip")
|
||||
assert calls == ["ok"]
|
||||
# Its row is still `running`, but nothing here is: the page does not call it going.
|
||||
assert scheduler.scheduler_snapshot(db)["ad_hoc"][0]["running"] is False
|
||||
|
||||
|
||||
def test_a_run_that_cannot_be_recorded_does_not_wedge_its_job(db, monkeypatch):
|
||||
@@ -484,10 +493,21 @@ def test_an_ad_hoc_run_still_going_shows_although_a_newer_one_ended(db, make_use
|
||||
user = make_user("two@example.com")
|
||||
db.add(Channel(id="UC_long", title="Long one"))
|
||||
db.commit()
|
||||
jobruns.start("channel_backfill", RunTrigger.AD_HOC, user.id, "UC_long")
|
||||
scheduler._ad_hoc("channel_backfill", lambda: {"skipped": "at capacity"}, user.id, "UC_short")
|
||||
(row,) = scheduler.scheduler_snapshot(db)["ad_hoc"]
|
||||
rows = []
|
||||
|
||||
def long_one():
|
||||
scheduler._ad_hoc(
|
||||
"channel_backfill", lambda: {"skipped": "at capacity"}, user.id, "UC_short"
|
||||
)
|
||||
rows.extend(scheduler.scheduler_snapshot(db)["ad_hoc"])
|
||||
return {}
|
||||
|
||||
scheduler._ad_hoc("channel_backfill", long_one, user.id, "UC_long")
|
||||
(row,) = rows
|
||||
assert (row["running"], row["target"], row["target_title"]) == (True, "UC_long", "Long one")
|
||||
# The outcome beside it is the long one's (none yet), never the short one's skip.
|
||||
assert (row["status"], row["result"], row["last_error"]) == (None, None, None)
|
||||
assert [r["status"] for r in row["recent"]] == ["running", "ok"]
|
||||
|
||||
|
||||
def test_an_interval_change_lands_with_its_audit_row_or_not_at_all(
|
||||
|
||||
Reference in New Issue
Block a user