feat(config): job intervals live in sysconfig, every setting says when it applies, the unread SponsorBlock default goes
An interval override was a row of its own table beside the one store every other admin knob uses; it is now the `job_interval_<job>` sysconfig key (migration 0110 copies them over and drops the table). The Scheduler page stays its one editor: the Configuration page and the setup wizard neither list nor write those keys, since a write there would skip the reschedule. Each ConfigSpec now states `applies` (required, no default). Every key is `live`, held by a scan that finds no read at import time; a `restart` key is refused until the page can say so. A second scan requires a reader for every key on the Configuration page — `sponsorblock_default` had none since it was added, so it leaves the registry, the translations and the store.
This commit is contained in:
@@ -12,7 +12,7 @@ from datetime import datetime, timedelta, timezone
|
||||
import pytest
|
||||
from sqlalchemy import select
|
||||
|
||||
from app import jobruns, logs, logsink, quota, scheduler, state
|
||||
from app import jobruns, logs, logsink, quota, scheduler, state, sysconfig
|
||||
from app.models import AuditLog, Channel, JobRun, Notification, QuotaEvent
|
||||
from app.runstatus import RunStatus, RunTrigger
|
||||
|
||||
@@ -411,3 +411,54 @@ def test_startup_closes_what_the_previous_process_left_running(db, monkeypatch):
|
||||
scheduler.shutdown_scheduler()
|
||||
(run,) = _runs(db, "plex_sync")
|
||||
assert run.status == "interrupted"
|
||||
|
||||
|
||||
def test_every_job_has_an_interval_and_every_interval_a_job():
|
||||
assert set(scheduler.JOB_FUNCS) == set(scheduler.JOB_INTERVALS)
|
||||
|
||||
|
||||
def test_a_stored_interval_is_what_the_job_starts_with(db, monkeypatch):
|
||||
sysconfig.set_value(db, "job_interval_rss_poll", 25)
|
||||
db.commit()
|
||||
monkeypatch.setattr(scheduler.settings, "scheduler_enabled", True)
|
||||
scheduler.start_scheduler()
|
||||
try:
|
||||
sched = scheduler._scheduler
|
||||
assert sched is not None
|
||||
assert sched.get_job("rss_poll").trigger.interval == timedelta(minutes=25)
|
||||
default = scheduler.JOB_INTERVALS["enrich"]
|
||||
assert sched.get_job("enrich").trigger.interval == timedelta(minutes=default)
|
||||
finally:
|
||||
scheduler.shutdown_scheduler()
|
||||
|
||||
|
||||
def test_an_interval_set_on_the_scheduler_page_is_stored_and_reschedules_the_job(
|
||||
db, make_user, as_user, live_scheduler
|
||||
):
|
||||
live_scheduler.add_job(lambda: None, "interval", minutes=10, id="backfill")
|
||||
admin = as_user(make_user(role="admin"))
|
||||
r = admin.patch("/api/admin/scheduler/jobs/backfill", json={"interval_minutes": 45})
|
||||
assert r.status_code == 200, r.text
|
||||
assert live_scheduler.get_job("backfill").trigger.interval == timedelta(minutes=45)
|
||||
db.expire_all()
|
||||
assert sysconfig.get_int(db, "job_interval_backfill") == 45
|
||||
assert scheduler.load_intervals(db)["backfill"] == 45
|
||||
# The Configuration page neither lists an interval nor writes one past the reschedule.
|
||||
assert (
|
||||
admin.patch("/api/admin/config/job_interval_backfill", json={"value": 5}).status_code == 404
|
||||
)
|
||||
assert admin.delete("/api/admin/config/job_interval_backfill").status_code == 404
|
||||
assert "scheduler" not in admin.get("/api/admin/config").json()["groups"]
|
||||
db.expire_all()
|
||||
assert sysconfig.get_int(db, "job_interval_backfill") == 45
|
||||
|
||||
|
||||
def test_the_setup_wizard_does_not_write_an_interval(db):
|
||||
from fastapi import HTTPException
|
||||
|
||||
from app.routes.setup import setup_config
|
||||
|
||||
with pytest.raises(HTTPException) as refused:
|
||||
setup_config({"job_interval_rss_poll": 5}, db)
|
||||
assert refused.value.status_code == 400
|
||||
assert setup_config({"quota_daily_budget": 5000}, db)["saved"] == ["quota_daily_budget"]
|
||||
|
||||
Reference in New Issue
Block a user