Add persistent job run queue
This commit is contained in:
parent
2bd0651478
commit
0b3b1b2731
8 changed files with 1047 additions and 27 deletions
|
|
@ -92,6 +92,35 @@ def test_runs_page_renders_completed_execution_end_time_as_relative_hoverable_ti
|
|||
assert ">2 hours ago<" in body
|
||||
|
||||
|
||||
def test_runs_page_renders_queued_execution_table() -> None:
|
||||
body = str(
|
||||
runs_page(
|
||||
queued_executions=(
|
||||
{
|
||||
"source": "Queued source",
|
||||
"slug": "queued-source",
|
||||
"job_id": 7,
|
||||
"execution_id": 42,
|
||||
"queued_at": "2 minutes ago",
|
||||
"queued_at_iso": "2026-03-30T12:28:00+00:00",
|
||||
"queue_position": 1,
|
||||
"status": "Queued",
|
||||
"status_tone": "idle",
|
||||
"run_label": "Queued",
|
||||
"run_disabled": True,
|
||||
"run_post_path": "/actions/jobs/7/run-now",
|
||||
"cancel_post_path": "/actions/queued-executions/42/cancel",
|
||||
},
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
assert "Queued job executions" in body
|
||||
assert "queued-source" in body
|
||||
assert ">Queued<" in body
|
||||
assert "/actions/queued-executions/42/cancel" in body
|
||||
|
||||
|
||||
def test_root_get_serves_datastar_shim() -> None:
|
||||
async def run() -> None:
|
||||
client = create_app().test_client()
|
||||
|
|
@ -1069,7 +1098,8 @@ def test_render_runs_shows_running_upcoming_and_completed_tables(
|
|||
body = str(await render_runs(app))
|
||||
|
||||
assert "Running job executions" in body
|
||||
assert "Upcoming jobs" in body
|
||||
assert "Queued job executions" in body
|
||||
assert "Scheduled jobs" in body
|
||||
assert "Completed job executions" in body
|
||||
assert "runs-render-source" in body
|
||||
assert f"/job/{job.id}/execution/{execution.get_id()}/logs" in body
|
||||
|
|
@ -1089,12 +1119,214 @@ def test_render_runs_shows_empty_state_rows(monkeypatch, tmp_path: Path) -> None
|
|||
body = str(await render_runs(app))
|
||||
|
||||
assert body.count("No job executions are running.") == 1
|
||||
assert "No queued executions are waiting." in body
|
||||
assert "No jobs are scheduled." in body
|
||||
assert "No job executions have completed yet." in body
|
||||
|
||||
asyncio.run(run())
|
||||
|
||||
|
||||
def test_render_runs_shows_queued_execution_separately_from_scheduled_jobs(
|
||||
monkeypatch, tmp_path: Path
|
||||
) -> None:
|
||||
db_path = tmp_path / "runs-queued-render.db"
|
||||
log_dir = tmp_path / "out" / "logs"
|
||||
monkeypatch.setenv("REPUBLISHER_DB_PATH", str(db_path))
|
||||
app = create_app()
|
||||
app.config["REPUB_LOG_DIR"] = log_dir
|
||||
|
||||
queued_source = create_source(
|
||||
name="Queued source",
|
||||
slug="queued-source",
|
||||
source_type="feed",
|
||||
notes="",
|
||||
spider_arguments="",
|
||||
enabled=True,
|
||||
cron_minute="*/5",
|
||||
cron_hour="*",
|
||||
cron_day_of_month="*",
|
||||
cron_day_of_week="*",
|
||||
cron_month="*",
|
||||
feed_url="https://example.com/queued.xml",
|
||||
)
|
||||
create_source(
|
||||
name="Scheduled source",
|
||||
slug="scheduled-source",
|
||||
source_type="feed",
|
||||
notes="",
|
||||
spider_arguments="",
|
||||
enabled=True,
|
||||
cron_minute="*/5",
|
||||
cron_hour="*",
|
||||
cron_day_of_month="*",
|
||||
cron_day_of_week="*",
|
||||
cron_month="*",
|
||||
feed_url="https://example.com/scheduled.xml",
|
||||
)
|
||||
queued_job = Job.get(Job.source == queued_source)
|
||||
queued_execution = JobExecution.create(
|
||||
job=queued_job,
|
||||
running_status=JobExecutionStatus.PENDING,
|
||||
)
|
||||
|
||||
async def run() -> None:
|
||||
body = str(await render_runs(app))
|
||||
|
||||
assert "Queued job executions" in body
|
||||
assert "Scheduled jobs" in body
|
||||
assert "queued-source" in body
|
||||
assert "scheduled-source" in body
|
||||
assert (
|
||||
f"/actions/queued-executions/{int(queued_execution.get_id())}/cancel"
|
||||
in body
|
||||
)
|
||||
|
||||
asyncio.run(run())
|
||||
|
||||
|
||||
def test_render_runs_shows_cancel_button_for_running_row_with_queued_follow_up(
|
||||
monkeypatch, tmp_path: Path
|
||||
) -> None:
|
||||
db_path = tmp_path / "runs-cancel-follow-up.db"
|
||||
log_dir = tmp_path / "out" / "logs"
|
||||
monkeypatch.setenv("REPUBLISHER_DB_PATH", str(db_path))
|
||||
app = create_app()
|
||||
app.config["REPUB_LOG_DIR"] = log_dir
|
||||
|
||||
source = create_source(
|
||||
name="Busy source",
|
||||
slug="busy-source",
|
||||
source_type="feed",
|
||||
notes="",
|
||||
spider_arguments="",
|
||||
enabled=True,
|
||||
cron_minute="*/5",
|
||||
cron_hour="*",
|
||||
cron_day_of_month="*",
|
||||
cron_day_of_week="*",
|
||||
cron_month="*",
|
||||
feed_url="https://example.com/busy.xml",
|
||||
)
|
||||
job = Job.get(Job.source == source)
|
||||
running_execution = JobExecution.create(
|
||||
job=job,
|
||||
started_at=datetime(2026, 3, 30, 12, 0, tzinfo=UTC),
|
||||
running_status=JobExecutionStatus.RUNNING,
|
||||
)
|
||||
pending_execution = JobExecution.create(
|
||||
job=job,
|
||||
running_status=JobExecutionStatus.PENDING,
|
||||
)
|
||||
|
||||
async def run() -> None:
|
||||
body = str(await render_runs(app))
|
||||
|
||||
assert f"/job/{job.id}/execution/{int(running_execution.get_id())}/logs" in body
|
||||
assert (
|
||||
f"/actions/queued-executions/{int(pending_execution.get_id())}/cancel"
|
||||
in body
|
||||
)
|
||||
assert ">Cancel<" in body
|
||||
|
||||
asyncio.run(run())
|
||||
|
||||
|
||||
def test_cancel_queued_execution_action_deletes_pending_row_without_touching_running_execution(
|
||||
monkeypatch, tmp_path: Path
|
||||
) -> None:
|
||||
db_path = tmp_path / "cancel-queued-action.db"
|
||||
log_dir = tmp_path / "out" / "logs"
|
||||
monkeypatch.setenv("REPUBLISHER_DB_PATH", str(db_path))
|
||||
|
||||
async def run() -> None:
|
||||
app = create_app()
|
||||
app.config["REPUB_LOG_DIR"] = log_dir
|
||||
client = app.test_client()
|
||||
|
||||
source = create_source(
|
||||
name="Busy source",
|
||||
slug="busy-source",
|
||||
source_type="feed",
|
||||
notes="",
|
||||
spider_arguments="",
|
||||
enabled=True,
|
||||
cron_minute="*/5",
|
||||
cron_hour="*",
|
||||
cron_day_of_month="*",
|
||||
cron_day_of_week="*",
|
||||
cron_month="*",
|
||||
feed_url="https://example.com/busy.xml",
|
||||
)
|
||||
job = Job.get(Job.source == source)
|
||||
running_execution = JobExecution.create(
|
||||
job=job,
|
||||
started_at=datetime(2026, 3, 30, 12, 0, tzinfo=UTC),
|
||||
running_status=JobExecutionStatus.RUNNING,
|
||||
)
|
||||
pending_execution = JobExecution.create(
|
||||
job=job,
|
||||
running_status=JobExecutionStatus.PENDING,
|
||||
)
|
||||
|
||||
response = await client.post(
|
||||
f"/actions/queued-executions/{int(pending_execution.get_id())}/cancel"
|
||||
)
|
||||
|
||||
assert response.status_code == 204
|
||||
assert JobExecution.get_or_none(id=int(pending_execution.get_id())) is None
|
||||
assert (
|
||||
JobExecution.get_by_id(int(running_execution.get_id())).running_status
|
||||
== JobExecutionStatus.RUNNING
|
||||
)
|
||||
|
||||
asyncio.run(run())
|
||||
|
||||
|
||||
def test_toggle_job_enabled_action_removes_queued_execution(
|
||||
monkeypatch, tmp_path: Path
|
||||
) -> None:
|
||||
db_path = tmp_path / "toggle-removes-queue.db"
|
||||
monkeypatch.setenv("REPUBLISHER_DB_PATH", str(db_path))
|
||||
|
||||
async def run() -> None:
|
||||
app = create_app()
|
||||
client = app.test_client()
|
||||
|
||||
source = create_source(
|
||||
name="Queued source",
|
||||
slug="queued-source",
|
||||
source_type="feed",
|
||||
notes="",
|
||||
spider_arguments="",
|
||||
enabled=True,
|
||||
cron_minute="*/5",
|
||||
cron_hour="*",
|
||||
cron_day_of_month="*",
|
||||
cron_day_of_week="*",
|
||||
cron_month="*",
|
||||
feed_url="https://example.com/queued.xml",
|
||||
)
|
||||
job = Job.get(Job.source == source)
|
||||
queued_execution = JobExecution.create(
|
||||
job=job,
|
||||
running_status=JobExecutionStatus.PENDING,
|
||||
)
|
||||
|
||||
response = await client.post(f"/actions/jobs/{job.id}/toggle-enabled")
|
||||
|
||||
assert response.status_code == 204
|
||||
assert Job.get_by_id(job.id).enabled is False
|
||||
assert JobExecution.get_or_none(id=int(queued_execution.get_id())) is None
|
||||
body = str(await render_runs(app))
|
||||
assert (
|
||||
f"/actions/queued-executions/{int(queued_execution.get_id())}/cancel"
|
||||
not in body
|
||||
)
|
||||
assert "Disabled" in body
|
||||
|
||||
asyncio.run(run())
|
||||
|
||||
|
||||
def test_render_execution_logs_uses_app_route(monkeypatch, tmp_path: Path) -> None:
|
||||
db_path = tmp_path / "logs-render.db"
|
||||
monkeypatch.setenv("REPUBLISHER_DB_PATH", str(db_path))
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue