import { expect, test } from "@playwright/test"; import { apiPathPattern, mockApi, primeMockSession } from "./support/network.js"; const cronTasks = [ { id: "economic.transfer_queue", name: "Process e-conomic transfer queue", description: "Processes pending e-conomic transfer queue jobs.", module: "economic", schedule: { type: "interval", seconds: 30 }, default_schedule: { type: "interval", seconds: 30 }, enabled: true, default_enabled: true, estimated_duration_ms: 2400, next_run_at: "2026-07-09 12:01:00", last_status: "succeeded", due: true, }, { id: "system.sync_logs", name: "Sync logs", description: "Flushes application logs.", module: "system", schedule: { type: "interval", seconds: 300 }, default_schedule: { type: "interval", seconds: 300 }, enabled: false, default_enabled: true, estimated_duration_ms: 900, next_run_at: "2026-07-09 12:05:00", last_status: "failed", last_error: "Redis unavailable", due: false, }, ]; function cronListPayload(tasks = cronTasks) { return { success: true, data: { tasks, summary: { total: tasks.length, enabled: tasks.filter((task) => task.enabled).length, due: tasks.filter((task) => task.enabled && task.due).length, }, }, meta: {}, includes: {}, }; } test.describe("Superuser cron operations", () => { test.beforeEach(async ({ page }) => { await mockApi(page, { authenticated: true, permissions: [ "superuser", "superuser_cron_view", "SUPERUSER_RUN_CRON", "superuser_cron_manage", "superuser_coolify_manage", ], }); await primeMockSession(page, { token: "superuser-cron-token", bootPath: null }); }); test("superusers can inspect, run, toggle, and reschedule cron tasks", async ({ page }) => { const patchPayloads: Array> = []; const runPayloads: Array> = []; const deployPayloads: Array> = []; let currentTasks = cronTasks.map((task) => ({ ...task, schedule: { ...task.schedule } })); await page.route(apiPathPattern("/superuser/cron"), async (route) => { if (route.request().method() !== "GET") { await route.fallback(); return; } await route.fulfill({ status: 200, contentType: "application/json", body: JSON.stringify(cronListPayload(currentTasks)), }); }); await page.route(apiPathPattern("/superuser/cron/runs"), async (route) => { await route.fulfill({ status: 200, contentType: "application/json", body: JSON.stringify({ success: true, data: { runs: [ { id: 41, task_id: "economic.transfer_queue", source: "automatic", status: "succeeded", started_at: "2026-07-09 12:00:00", duration_ms: 2100, }, ], }, meta: {}, includes: {}, }), }); }); await page.route(apiPathPattern("/superuser/cron/workers"), async (route) => { if (route.request().method() !== "GET") { await route.fallback(); return; } await route.fulfill({ status: 200, contentType: "application/json", body: JSON.stringify({ success: true, data: { workers: [ { worker_id: "release-stable-cron-worker", name: "release-stable-cron-worker", hostname: "coolify-cron-1", source: "coolify_worker", status: "running", stale: false, last_heartbeat_at: "2026-07-09 12:01:30", last_run_count: 1, commit_sha: "abcdef1234567890", }, ], summary: { total: 1, running: 1, stale: 0, }, deployment: { ok: true, channel: { id: 1, slug: "stable", name: "Stable", }, target: { id: 71, app: "cron", coolify_service_uuid: "cron-worker-uuid", auto_deploy: false, }, }, }, meta: {}, includes: {}, }), }); }); await page.route(apiPathPattern("/superuser/cron/workers/deploy"), async (route) => { deployPayloads.push(route.request().postDataJSON() as Record); await route.fulfill({ status: 200, contentType: "application/json", body: JSON.stringify({ success: true, data: { ok: true, target: { id: 71, app: "cron", coolify_service_uuid: "cron-worker-uuid", auto_deploy: false, }, }, meta: {}, includes: {}, }), }); }); await page.route(apiPathPattern("/superuser/cron/run"), async (route) => { runPayloads.push(route.request().postDataJSON() as Record); await route.fulfill({ status: 200, contentType: "application/json", body: JSON.stringify({ success: true, data: { id: 42, task_id: "economic.transfer_queue", source: "manual", status: "succeeded", started_at: "2026-07-09 12:02:00", duration_ms: 1800, }, meta: {}, includes: {}, }), }); }); await page.route(apiPathPattern("/superuser/cron/config"), async (route) => { const payload = route.request().postDataJSON() as Record; patchPayloads.push(payload); currentTasks = currentTasks.map((task) => { if (task.id !== payload.task_id) { return task; } return { ...task, ...(typeof payload.enabled === "boolean" ? { enabled: payload.enabled } : {}), ...(payload.schedule ? { schedule: payload.schedule as { type: string; seconds: number } } : {}), }; }); await route.fulfill({ status: 200, contentType: "application/json", body: JSON.stringify(cronListPayload(currentTasks)), }); }); await page.goto("/superuser/system/cron", { waitUntil: "domcontentloaded" }); await expect(page.getByTestId("superuser-cron-page")).toBeVisible(); await expect(page.getByTestId("cron-task-row-economic.transfer_queue")).toContainText( "Process e-conomic transfer queue" ); await expect(page.getByTestId("cron-total-tasks")).toContainText("2"); await expect(page.getByTestId("cron-worker-panel")).toContainText("release-stable-cron-worker"); await expect(page.getByTestId("cron-worker-running")).toContainText("1"); await expect(page.getByTestId("cron-worker-target")).toContainText("cron-worker-uuid"); await Promise.all([ page.waitForResponse( (response) => response.url().includes("/superuser/cron/workers/deploy") && response.request().method() === "POST" ), page.getByTestId("cron-worker-deploy").click(), ]); expect(deployPayloads).toEqual([{}]); await Promise.all([ page.waitForResponse( (response) => response.url().includes("/superuser/cron/run") && response.request().method() === "POST" ), page.getByTestId("cron-task-run-economic.transfer_queue").click(), ]); expect(runPayloads).toEqual([{ task_id: "economic.transfer_queue", force: false }]); await expect(page.getByTestId("cron-run-history")).toContainText("manual"); await page.getByTestId("cron-task-toggle-system.sync_logs").click(); expect(patchPayloads.at(-1)).toMatchObject({ task_id: "system.sync_logs", enabled: true }); await page.getByTestId("cron-task-interval-economic.transfer_queue").fill("120"); await page.getByTestId("cron-task-save-economic.transfer_queue").click(); expect(patchPayloads.at(-1)).toMatchObject({ task_id: "economic.transfer_queue", schedule: { type: "interval", seconds: 120 }, }); }); });