2024-01-01 14:58:21 -05:00
|
|
|
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
|
2023-11-10 16:29:09 -05:00
|
|
|
import { assertEquals, assertThrows } from "./test_util.ts";
|
2023-12-26 20:30:26 -05:00
|
|
|
|
|
|
|
// @ts-ignore This is not publicly typed namespace, but it's there for sure.
|
|
|
|
const {
|
2023-11-30 16:51:56 -05:00
|
|
|
formatToCronSchedule,
|
|
|
|
parseScheduleToString,
|
2023-12-26 20:30:26 -05:00
|
|
|
// @ts-expect-error TypeScript (as of 3.7) does not support indexing namespaces by symbol
|
|
|
|
} = Deno[Deno.internal];
|
2023-11-01 14:57:55 -04:00
|
|
|
|
|
|
|
const sleep = (time: number) => new Promise((r) => setTimeout(r, time));
|
|
|
|
|
|
|
|
Deno.test(function noNameTest() {
|
|
|
|
assertThrows(
|
|
|
|
// @ts-ignore test
|
|
|
|
() => Deno.cron(),
|
|
|
|
TypeError,
|
|
|
|
"Deno.cron requires a unique name",
|
|
|
|
);
|
|
|
|
});
|
|
|
|
|
|
|
|
Deno.test(function noSchedule() {
|
|
|
|
assertThrows(
|
|
|
|
// @ts-ignore test
|
|
|
|
() => Deno.cron("foo"),
|
|
|
|
TypeError,
|
|
|
|
"Deno.cron requires a valid schedule",
|
|
|
|
);
|
|
|
|
});
|
|
|
|
|
|
|
|
Deno.test(function noHandler() {
|
|
|
|
assertThrows(
|
|
|
|
// @ts-ignore test
|
|
|
|
() => Deno.cron("foo", "*/1 * * * *"),
|
|
|
|
TypeError,
|
|
|
|
"Deno.cron requires a handler",
|
|
|
|
);
|
|
|
|
});
|
|
|
|
|
|
|
|
Deno.test(function invalidNameTest() {
|
|
|
|
assertThrows(
|
|
|
|
() => Deno.cron("abc[]", "*/1 * * * *", () => {}),
|
|
|
|
TypeError,
|
|
|
|
"Invalid cron name",
|
|
|
|
);
|
|
|
|
assertThrows(
|
|
|
|
() => Deno.cron("a**bc", "*/1 * * * *", () => {}),
|
|
|
|
TypeError,
|
|
|
|
"Invalid cron name",
|
|
|
|
);
|
|
|
|
assertThrows(
|
|
|
|
() => Deno.cron("abc<>", "*/1 * * * *", () => {}),
|
|
|
|
TypeError,
|
|
|
|
"Invalid cron name",
|
|
|
|
);
|
|
|
|
assertThrows(
|
|
|
|
() => Deno.cron(";']", "*/1 * * * *", () => {}),
|
|
|
|
TypeError,
|
|
|
|
"Invalid cron name",
|
|
|
|
);
|
|
|
|
assertThrows(
|
|
|
|
() =>
|
|
|
|
Deno.cron(
|
|
|
|
"0000000000000000000000000000000000000000000000000000000000000000000000",
|
|
|
|
"*/1 * * * *",
|
|
|
|
() => {},
|
|
|
|
),
|
|
|
|
TypeError,
|
|
|
|
"Cron name is too long",
|
|
|
|
);
|
|
|
|
});
|
|
|
|
|
|
|
|
Deno.test(function invalidScheduleTest() {
|
|
|
|
assertThrows(
|
|
|
|
() => Deno.cron("abc", "bogus", () => {}),
|
|
|
|
TypeError,
|
|
|
|
"Invalid cron schedule",
|
|
|
|
);
|
|
|
|
assertThrows(
|
|
|
|
() => Deno.cron("abc", "* * * * * *", () => {}),
|
|
|
|
TypeError,
|
|
|
|
"Invalid cron schedule",
|
|
|
|
);
|
|
|
|
assertThrows(
|
|
|
|
() => Deno.cron("abc", "* * * *", () => {}),
|
|
|
|
TypeError,
|
|
|
|
"Invalid cron schedule",
|
|
|
|
);
|
|
|
|
assertThrows(
|
|
|
|
() => Deno.cron("abc", "m * * * *", () => {}),
|
|
|
|
TypeError,
|
|
|
|
"Invalid cron schedule",
|
|
|
|
);
|
|
|
|
});
|
|
|
|
|
|
|
|
Deno.test(function invalidBackoffScheduleTest() {
|
|
|
|
assertThrows(
|
|
|
|
() =>
|
2023-11-16 17:19:00 -05:00
|
|
|
Deno.cron(
|
|
|
|
"abc",
|
|
|
|
"*/1 * * * *",
|
|
|
|
{ backoffSchedule: [1, 1, 1, 1, 1, 1] },
|
|
|
|
() => {},
|
|
|
|
),
|
2023-11-01 14:57:55 -04:00
|
|
|
TypeError,
|
|
|
|
"Invalid backoff schedule",
|
|
|
|
);
|
|
|
|
assertThrows(
|
|
|
|
() =>
|
2023-11-16 17:19:00 -05:00
|
|
|
Deno.cron("abc", "*/1 * * * *", { backoffSchedule: [3600001] }, () => {}),
|
2023-11-01 14:57:55 -04:00
|
|
|
TypeError,
|
|
|
|
"Invalid backoff schedule",
|
|
|
|
);
|
|
|
|
});
|
|
|
|
|
|
|
|
Deno.test(async function tooManyCrons() {
|
|
|
|
const crons: Promise<void>[] = [];
|
|
|
|
const ac = new AbortController();
|
|
|
|
for (let i = 0; i <= 100; i++) {
|
2023-11-16 17:19:00 -05:00
|
|
|
const c = Deno.cron(
|
|
|
|
`abc_${i}`,
|
|
|
|
"*/1 * * * *",
|
|
|
|
{ signal: ac.signal },
|
|
|
|
() => {},
|
|
|
|
);
|
2023-11-01 14:57:55 -04:00
|
|
|
crons.push(c);
|
|
|
|
}
|
|
|
|
|
|
|
|
try {
|
|
|
|
assertThrows(
|
|
|
|
() => {
|
2023-11-16 17:19:00 -05:00
|
|
|
Deno.cron("next-cron", "*/1 * * * *", { signal: ac.signal }, () => {});
|
2023-11-01 14:57:55 -04:00
|
|
|
},
|
|
|
|
TypeError,
|
|
|
|
"Too many crons",
|
|
|
|
);
|
|
|
|
} finally {
|
|
|
|
ac.abort();
|
|
|
|
for (const c of crons) {
|
|
|
|
await c;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
Deno.test(async function duplicateCrons() {
|
|
|
|
const ac = new AbortController();
|
2023-11-16 17:19:00 -05:00
|
|
|
const c = Deno.cron("abc", "*/20 * * * *", { signal: ac.signal }, () => {});
|
2023-11-01 14:57:55 -04:00
|
|
|
try {
|
|
|
|
assertThrows(
|
|
|
|
() => Deno.cron("abc", "*/20 * * * *", () => {}),
|
|
|
|
TypeError,
|
|
|
|
"Cron with this name already exists",
|
|
|
|
);
|
|
|
|
} finally {
|
|
|
|
ac.abort();
|
|
|
|
await c;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
Deno.test(async function basicTest() {
|
|
|
|
Deno.env.set("DENO_CRON_TEST_SCHEDULE_OFFSET", "100");
|
|
|
|
|
|
|
|
let count = 0;
|
2023-11-10 16:29:09 -05:00
|
|
|
const { promise, resolve } = Promise.withResolvers<void>();
|
2023-11-01 14:57:55 -04:00
|
|
|
const ac = new AbortController();
|
2023-11-16 17:19:00 -05:00
|
|
|
const c = Deno.cron("abc", "*/20 * * * *", { signal: ac.signal }, () => {
|
2023-11-01 14:57:55 -04:00
|
|
|
count++;
|
|
|
|
if (count > 5) {
|
2023-11-10 16:29:09 -05:00
|
|
|
resolve();
|
2023-11-01 14:57:55 -04:00
|
|
|
}
|
2023-11-16 17:19:00 -05:00
|
|
|
});
|
2023-11-01 14:57:55 -04:00
|
|
|
try {
|
|
|
|
await promise;
|
|
|
|
} finally {
|
|
|
|
ac.abort();
|
|
|
|
await c;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2023-11-30 16:51:56 -05:00
|
|
|
Deno.test(async function basicTestWithJsonFormatScheduleExpression() {
|
|
|
|
Deno.env.set("DENO_CRON_TEST_SCHEDULE_OFFSET", "100");
|
|
|
|
|
|
|
|
let count = 0;
|
|
|
|
const { promise, resolve } = Promise.withResolvers<void>();
|
|
|
|
const ac = new AbortController();
|
|
|
|
const c = Deno.cron(
|
|
|
|
"abc",
|
|
|
|
{ minute: { every: 20 } },
|
|
|
|
{ signal: ac.signal },
|
|
|
|
() => {
|
|
|
|
count++;
|
|
|
|
if (count > 5) {
|
|
|
|
resolve();
|
|
|
|
}
|
|
|
|
},
|
|
|
|
);
|
|
|
|
try {
|
|
|
|
await promise;
|
|
|
|
} finally {
|
|
|
|
ac.abort();
|
|
|
|
await c;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2023-11-01 14:57:55 -04:00
|
|
|
Deno.test(async function multipleCrons() {
|
|
|
|
Deno.env.set("DENO_CRON_TEST_SCHEDULE_OFFSET", "100");
|
|
|
|
|
|
|
|
let count0 = 0;
|
|
|
|
let count1 = 0;
|
2023-11-10 16:29:09 -05:00
|
|
|
const { promise: promise0, resolve: resolve0 } = Promise.withResolvers<
|
|
|
|
void
|
|
|
|
>();
|
|
|
|
const { promise: promise1, resolve: resolve1 } = Promise.withResolvers<
|
|
|
|
void
|
|
|
|
>();
|
2023-11-01 14:57:55 -04:00
|
|
|
const ac = new AbortController();
|
2023-11-16 17:19:00 -05:00
|
|
|
const c0 = Deno.cron("abc", "*/20 * * * *", { signal: ac.signal }, () => {
|
2023-11-01 14:57:55 -04:00
|
|
|
count0++;
|
|
|
|
if (count0 > 5) {
|
2023-11-10 16:29:09 -05:00
|
|
|
resolve0();
|
2023-11-01 14:57:55 -04:00
|
|
|
}
|
2023-11-16 17:19:00 -05:00
|
|
|
});
|
|
|
|
const c1 = Deno.cron("xyz", "*/20 * * * *", { signal: ac.signal }, () => {
|
2023-11-01 14:57:55 -04:00
|
|
|
count1++;
|
|
|
|
if (count1 > 5) {
|
2023-11-10 16:29:09 -05:00
|
|
|
resolve1();
|
2023-11-01 14:57:55 -04:00
|
|
|
}
|
2023-11-16 17:19:00 -05:00
|
|
|
});
|
2023-11-01 14:57:55 -04:00
|
|
|
try {
|
|
|
|
await promise0;
|
|
|
|
await promise1;
|
|
|
|
} finally {
|
|
|
|
ac.abort();
|
|
|
|
await c0;
|
|
|
|
await c1;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
Deno.test(async function overlappingExecutions() {
|
|
|
|
Deno.env.set("DENO_CRON_TEST_SCHEDULE_OFFSET", "100");
|
|
|
|
|
|
|
|
let count = 0;
|
2023-11-10 16:29:09 -05:00
|
|
|
const { promise: promise0, resolve: resolve0 } = Promise.withResolvers<
|
|
|
|
void
|
|
|
|
>();
|
|
|
|
const { promise: promise1, resolve: resolve1 } = Promise.withResolvers<
|
|
|
|
void
|
|
|
|
>();
|
2023-11-01 14:57:55 -04:00
|
|
|
const ac = new AbortController();
|
2023-11-16 17:19:00 -05:00
|
|
|
const c = Deno.cron(
|
|
|
|
"abc",
|
|
|
|
"*/20 * * * *",
|
|
|
|
{ signal: ac.signal },
|
|
|
|
async () => {
|
|
|
|
resolve0();
|
|
|
|
count++;
|
|
|
|
await promise1;
|
|
|
|
},
|
|
|
|
);
|
2023-11-01 14:57:55 -04:00
|
|
|
try {
|
|
|
|
await promise0;
|
|
|
|
} finally {
|
|
|
|
await sleep(2000);
|
2023-11-10 16:29:09 -05:00
|
|
|
resolve1();
|
2023-11-01 14:57:55 -04:00
|
|
|
ac.abort();
|
|
|
|
await c;
|
|
|
|
}
|
|
|
|
assertEquals(count, 1);
|
|
|
|
});
|
|
|
|
|
2023-11-16 17:19:00 -05:00
|
|
|
Deno.test(async function retriesWithBackoffSchedule() {
|
|
|
|
Deno.env.set("DENO_CRON_TEST_SCHEDULE_OFFSET", "5000");
|
|
|
|
|
|
|
|
let count = 0;
|
|
|
|
const ac = new AbortController();
|
|
|
|
const c = Deno.cron("abc", "*/20 * * * *", {
|
|
|
|
signal: ac.signal,
|
|
|
|
backoffSchedule: [10, 20],
|
|
|
|
}, async () => {
|
|
|
|
count += 1;
|
|
|
|
await sleep(10);
|
|
|
|
throw new TypeError("cron error");
|
|
|
|
});
|
|
|
|
try {
|
|
|
|
await sleep(6000);
|
|
|
|
} finally {
|
|
|
|
ac.abort();
|
|
|
|
await c;
|
|
|
|
}
|
|
|
|
|
|
|
|
// The cron should have executed 3 times (1st attempt and 2 retries).
|
|
|
|
assertEquals(count, 3);
|
|
|
|
});
|
|
|
|
|
|
|
|
Deno.test(async function retriesWithBackoffScheduleOldApi() {
|
2023-11-01 14:57:55 -04:00
|
|
|
Deno.env.set("DENO_CRON_TEST_SCHEDULE_OFFSET", "5000");
|
|
|
|
|
|
|
|
let count = 0;
|
|
|
|
const ac = new AbortController();
|
2023-11-16 17:19:00 -05:00
|
|
|
const c = Deno.cron("abc2", "*/20 * * * *", async () => {
|
2023-11-01 14:57:55 -04:00
|
|
|
count += 1;
|
|
|
|
await sleep(10);
|
|
|
|
throw new TypeError("cron error");
|
2023-11-16 17:19:00 -05:00
|
|
|
}, {
|
|
|
|
signal: ac.signal,
|
|
|
|
backoffSchedule: [10, 20],
|
|
|
|
});
|
|
|
|
|
2023-11-01 14:57:55 -04:00
|
|
|
try {
|
|
|
|
await sleep(6000);
|
|
|
|
} finally {
|
|
|
|
ac.abort();
|
|
|
|
await c;
|
|
|
|
}
|
|
|
|
|
|
|
|
// The cron should have executed 3 times (1st attempt and 2 retries).
|
|
|
|
assertEquals(count, 3);
|
|
|
|
});
|
2023-11-30 16:51:56 -05:00
|
|
|
|
|
|
|
Deno.test("formatToCronSchedule - undefined value", () => {
|
|
|
|
const result = formatToCronSchedule();
|
|
|
|
assertEquals(result, "*");
|
|
|
|
});
|
|
|
|
|
|
|
|
Deno.test("formatToCronSchedule - number value", () => {
|
|
|
|
const result = formatToCronSchedule(5);
|
|
|
|
assertEquals(result, "5");
|
|
|
|
});
|
|
|
|
|
|
|
|
Deno.test("formatToCronSchedule - exact array value", () => {
|
|
|
|
const result = formatToCronSchedule({ exact: [1, 2, 3] });
|
|
|
|
assertEquals(result, "1,2,3");
|
|
|
|
});
|
|
|
|
|
|
|
|
Deno.test("formatToCronSchedule - exact number value", () => {
|
|
|
|
const result = formatToCronSchedule({ exact: 5 });
|
|
|
|
assertEquals(result, "5");
|
|
|
|
});
|
|
|
|
|
|
|
|
Deno.test("formatToCronSchedule - start, end, every values", () => {
|
|
|
|
const result = formatToCronSchedule({ start: 1, end: 10, every: 2 });
|
|
|
|
assertEquals(result, "1-10/2");
|
|
|
|
});
|
|
|
|
|
|
|
|
Deno.test("formatToCronSchedule - start, end values", () => {
|
|
|
|
const result = formatToCronSchedule({ start: 1, end: 10 });
|
|
|
|
assertEquals(result, "1-10");
|
|
|
|
});
|
|
|
|
|
|
|
|
Deno.test("formatToCronSchedule - start, every values", () => {
|
|
|
|
const result = formatToCronSchedule({ start: 1, every: 2 });
|
|
|
|
assertEquals(result, "1/2");
|
|
|
|
});
|
|
|
|
|
|
|
|
Deno.test("formatToCronSchedule - start value", () => {
|
|
|
|
const result = formatToCronSchedule({ start: 1 });
|
|
|
|
assertEquals(result, "1/1");
|
|
|
|
});
|
|
|
|
|
|
|
|
Deno.test("formatToCronSchedule - end, every values", () => {
|
|
|
|
assertThrows(
|
|
|
|
() => formatToCronSchedule({ end: 10, every: 2 }),
|
|
|
|
TypeError,
|
|
|
|
"Invalid cron schedule",
|
|
|
|
);
|
|
|
|
});
|
|
|
|
|
|
|
|
Deno.test("Parse CronSchedule to string", () => {
|
|
|
|
const result = parseScheduleToString({
|
|
|
|
minute: { exact: [1, 2, 3] },
|
|
|
|
hour: { start: 1, end: 10, every: 2 },
|
|
|
|
dayOfMonth: { exact: 5 },
|
|
|
|
month: { start: 1, end: 10 },
|
|
|
|
dayOfWeek: { start: 1, every: 2 },
|
|
|
|
});
|
|
|
|
assertEquals(result, "1,2,3 1-10/2 5 1-10 1/2");
|
|
|
|
});
|
|
|
|
|
|
|
|
Deno.test("Parse schedule to string - string", () => {
|
|
|
|
const result = parseScheduleToString("* * * * *");
|
|
|
|
assertEquals(result, "* * * * *");
|
|
|
|
});
|