summaryrefslogtreecommitdiff
path: root/tests/unit/cron_test.ts
diff options
context:
space:
mode:
Diffstat (limited to 'tests/unit/cron_test.ts')
-rw-r--r--tests/unit/cron_test.ts460
1 files changed, 460 insertions, 0 deletions
diff --git a/tests/unit/cron_test.ts b/tests/unit/cron_test.ts
new file mode 100644
index 000000000..02573a898
--- /dev/null
+++ b/tests/unit/cron_test.ts
@@ -0,0 +1,460 @@
+// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
+import { assertEquals, assertThrows } from "./test_util.ts";
+
+// @ts-ignore This is not publicly typed namespace, but it's there for sure.
+const {
+ formatToCronSchedule,
+ parseScheduleToString,
+ // @ts-expect-error TypeScript (as of 3.7) does not support indexing namespaces by symbol
+} = Deno[Deno.internal];
+
+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(
+ () =>
+ Deno.cron(
+ "abc",
+ "*/1 * * * *",
+ { backoffSchedule: [1, 1, 1, 1, 1, 1] },
+ () => {},
+ ),
+ TypeError,
+ "Invalid backoff schedule",
+ );
+ assertThrows(
+ () =>
+ Deno.cron("abc", "*/1 * * * *", { backoffSchedule: [3600001] }, () => {}),
+ TypeError,
+ "Invalid backoff schedule",
+ );
+});
+
+Deno.test(async function tooManyCrons() {
+ const crons: Promise<void>[] = [];
+ const ac = new AbortController();
+ for (let i = 0; i <= 100; i++) {
+ const c = Deno.cron(
+ `abc_${i}`,
+ "*/1 * * * *",
+ { signal: ac.signal },
+ () => {},
+ );
+ crons.push(c);
+ }
+
+ try {
+ assertThrows(
+ () => {
+ Deno.cron("next-cron", "*/1 * * * *", { signal: ac.signal }, () => {});
+ },
+ TypeError,
+ "Too many crons",
+ );
+ } finally {
+ ac.abort();
+ for (const c of crons) {
+ await c;
+ }
+ }
+});
+
+Deno.test(async function duplicateCrons() {
+ const ac = new AbortController();
+ const c = Deno.cron("abc", "*/20 * * * *", { signal: ac.signal }, () => {});
+ 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;
+ const { promise, resolve } = Promise.withResolvers<void>();
+ const ac = new AbortController();
+ const c = Deno.cron("abc", "*/20 * * * *", { signal: ac.signal }, () => {
+ count++;
+ if (count > 5) {
+ resolve();
+ }
+ });
+ try {
+ await promise;
+ } finally {
+ ac.abort();
+ await c;
+ }
+});
+
+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;
+ }
+});
+
+Deno.test(async function multipleCrons() {
+ Deno.env.set("DENO_CRON_TEST_SCHEDULE_OFFSET", "100");
+
+ let count0 = 0;
+ let count1 = 0;
+ const { promise: promise0, resolve: resolve0 } = Promise.withResolvers<
+ void
+ >();
+ const { promise: promise1, resolve: resolve1 } = Promise.withResolvers<
+ void
+ >();
+ const ac = new AbortController();
+ const c0 = Deno.cron("abc", "*/20 * * * *", { signal: ac.signal }, () => {
+ count0++;
+ if (count0 > 5) {
+ resolve0();
+ }
+ });
+ const c1 = Deno.cron("xyz", "*/20 * * * *", { signal: ac.signal }, () => {
+ count1++;
+ if (count1 > 5) {
+ resolve1();
+ }
+ });
+ 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;
+ const { promise: promise0, resolve: resolve0 } = Promise.withResolvers<
+ void
+ >();
+ const { promise: promise1, resolve: resolve1 } = Promise.withResolvers<
+ void
+ >();
+ const ac = new AbortController();
+ const c = Deno.cron(
+ "abc",
+ "*/20 * * * *",
+ { signal: ac.signal },
+ async () => {
+ resolve0();
+ count++;
+ await promise1;
+ },
+ );
+ try {
+ await promise0;
+ } finally {
+ await sleep(2000);
+ resolve1();
+ ac.abort();
+ await c;
+ }
+ assertEquals(count, 1);
+});
+
+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() {
+ Deno.env.set("DENO_CRON_TEST_SCHEDULE_OFFSET", "5000");
+
+ let count = 0;
+ const ac = new AbortController();
+ const c = Deno.cron("abc2", "*/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("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, "* * * * *");
+});
+
+Deno.test("error on two handlers", () => {
+ assertThrows(
+ () => {
+ // @ts-ignore test
+ Deno.cron("abc", "* * * * *", () => {}, () => {});
+ },
+ TypeError,
+ "Deno.cron requires a single handler",
+ );
+});
+
+Deno.test("Parse test", () => {
+ assertEquals(
+ parseScheduleToString({
+ minute: 3,
+ }),
+ "3 * * * *",
+ );
+ assertEquals(
+ parseScheduleToString({
+ hour: { every: 2 },
+ }),
+ "0 */2 * * *",
+ );
+ assertEquals(
+ parseScheduleToString({
+ dayOfMonth: { every: 10 },
+ }),
+ "0 0 */10 * *",
+ );
+ assertEquals(
+ parseScheduleToString({
+ month: { every: 3 },
+ }),
+ "0 0 1 */3 *",
+ );
+ assertEquals(
+ parseScheduleToString({
+ dayOfWeek: { every: 2 },
+ }),
+ "0 0 * * */2",
+ );
+ assertEquals(
+ parseScheduleToString({
+ minute: 3,
+ hour: { every: 2 },
+ }),
+ "3 */2 * * *",
+ );
+ assertEquals(
+ parseScheduleToString({
+ dayOfMonth: { start: 1, end: 10 },
+ }),
+ "0 0 1-10 * *",
+ );
+ assertEquals(
+ parseScheduleToString({
+ minute: { every: 10 },
+ dayOfMonth: { every: 5 },
+ }),
+ "*/10 * */5 * *",
+ );
+ assertEquals(
+ parseScheduleToString({
+ hour: { every: 3 },
+ month: { every: 2 },
+ }),
+ "0 */3 * */2 *",
+ );
+ assertEquals(
+ parseScheduleToString({
+ minute: { every: 5 },
+ month: { every: 2 },
+ }),
+ "*/5 * * */2 *",
+ );
+});