Files
pleno-vue/tests/e2e/superuser-cron.spec.ts
T

263 lines
8.2 KiB
TypeScript

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<Record<string, unknown>> = [];
const runPayloads: Array<Record<string, unknown>> = [];
const deployPayloads: Array<Record<string, unknown>> = [];
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<string, unknown>);
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<string, unknown>);
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<string, unknown>;
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 },
});
});
});