| 1 | // The self-hosted scheduler's cron matching (deploy/self-host/scheduler.mjs). |
| 2 | import assert from "node:assert/strict"; |
| 3 | import { test } from "node:test"; |
| 4 | |
| 5 | import { due, fieldValues, matches, run } from "../../deploy/self-host/scheduler.mjs"; |
| 6 | |
| 7 | const at = (iso) => new Date(iso); |
| 8 | |
| 9 | test("fields read numbers, ranges, lists and steps", () => { |
| 10 | assert.deepEqual([...fieldValues("*/15", { min: 0, max: 59 })], [0, 15, 30, 45]); |
| 11 | assert.deepEqual([...fieldValues("1-5", { min: 0, max: 59 })], [1, 2, 3, 4, 5]); |
| 12 | assert.deepEqual([...fieldValues("3,7,10-20/5", { min: 0, max: 59 })], [3, 7, 10, 15, 20]); |
| 13 | assert.equal(fieldValues("60", { min: 0, max: 59 }), null); |
| 14 | assert.equal(fieldValues("*/0", { min: 0, max: 59 }), null); |
| 15 | assert.equal(fieldValues("x", { min: 0, max: 59 }), null); |
| 16 | }); |
| 17 | |
| 18 | test("the services' crons match when hosted g1t's would fire", () => { |
| 19 | assert.ok(matches("37 * * * *", at("2026-10-06T12:37:00Z"))); |
| 20 | assert.ok(!matches("37 * * * *", at("2026-10-06T12:38:00Z"))); |
| 21 | assert.ok(matches("*/15 * * * *", at("2026-10-06T12:45:00Z"))); |
| 22 | assert.ok(matches("41 3 * * *", at("2026-10-06T03:41:59Z"))); |
| 23 | assert.ok(!matches("41 3 * * *", at("2026-10-06T04:41:00Z"))); |
| 24 | assert.ok(matches("* * * * *", at("2026-10-06T00:00:00Z"))); |
| 25 | // Sunday is 0 or 7; with both day fields given, either may match. |
| 26 | assert.ok(matches("0 0 * * 7", at("2026-10-04T00:00:00Z"))); |
| 27 | assert.ok(matches("0 0 1 * 1", at("2026-10-05T00:00:00Z"))); |
| 28 | assert.ok(!matches("bad cron", at("2026-10-06T00:00:00Z"))); |
| 29 | }); |
| 30 | |
| 31 | test("what is due is each matching cron of each service", () => { |
| 32 | const schedules = [ |
| 33 | { worker: "g1t-packages", crons: ["37 * * * *"] }, |
| 34 | { worker: "g1t-webhooks", crons: ["* * * * *"] }, |
| 35 | { worker: "g1t-identity", crons: ["*/15 * * * *"] }, |
| 36 | ]; |
| 37 | assert.deepEqual(due(schedules, at("2026-10-06T12:37:00Z")), [ |
| 38 | { worker: "g1t-packages", cron: "37 * * * *" }, |
| 39 | { worker: "g1t-webhooks", cron: "* * * * *" }, |
| 40 | ]); |
| 41 | }); |
| 42 | |
| 43 | test("a run asks Wrangler for the handler, and a slow one is not doubled", async () => { |
| 44 | const asked = []; |
| 45 | let finish; |
| 46 | const slow = (url, init) => { |
| 47 | asked.push({ url, body: JSON.parse(init.body) }); |
| 48 | return new Promise((resolve) => { |
| 49 | finish = () => resolve(new Response(JSON.stringify({ success: true, result: { outcome: "ok" } }))); |
| 50 | }); |
| 51 | }; |
| 52 | const job = { worker: "g1t-webhooks", cron: "* * * * *" }; |
| 53 | const first = run("http://127.0.0.1:8787", job, { fetch: slow }); |
| 54 | // The minute after, it is still running: skipped, not started again. |
| 55 | assert.equal(await run("http://127.0.0.1:8787", job, { fetch: slow }), false); |
| 56 | finish(); |
| 57 | assert.equal(await first, true); |
| 58 | assert.deepEqual(asked, [ |
| 59 | { url: "http://127.0.0.1:8787/cdn-cgi/local/explorer/api/local/scheduled?worker=g1t-webhooks", body: { cron: "* * * * *" } }, |
| 60 | ]); |
| 61 | const failing = async () => new Response(JSON.stringify({ success: true, result: { outcome: "exception" } })); |
| 62 | assert.equal(await run("http://127.0.0.1:8787", job, { fetch: failing }), false); |
| 63 | }); |