112 lines
3.2 KiB
TypeScript
112 lines
3.2 KiB
TypeScript
import { describe, expect, it } from "@suaveplan/testing/runner";
|
|
import fc from "fast-check";
|
|
import { parseCron } from "../src/index.js";
|
|
|
|
const EXPRESSIONS = [
|
|
"* * * * *",
|
|
"0 * * * *",
|
|
"*/5 * * * *",
|
|
"*/15 * * * *",
|
|
"0 9 * * *",
|
|
"0 9 * * MON-FRI",
|
|
"30 14 * * 0",
|
|
"0 0 1 * *",
|
|
"0 0 1 1 *",
|
|
"0,15,30,45 9-17 * * MON-FRI",
|
|
"0 10-12/2 * * *",
|
|
];
|
|
|
|
const TIMEZONES = [
|
|
"UTC",
|
|
"America/New_York",
|
|
"Europe/London",
|
|
"Australia/Sydney",
|
|
];
|
|
|
|
const timestampArb = fc.integer({
|
|
min: Date.UTC(2026, 0, 1),
|
|
max: Date.UTC(2028, 0, 1),
|
|
});
|
|
|
|
describe("property — nextRun > after", () => {
|
|
it("returns a strictly later instant for every supported expression", {
|
|
timeout: 30000,
|
|
}, () => {
|
|
fc.assert(
|
|
fc.property(
|
|
fc.constantFrom(...EXPRESSIONS),
|
|
timestampArb,
|
|
fc.constantFrom(...TIMEZONES),
|
|
(e, t, tz) => {
|
|
const next = parseCron(e).nextRun(t, tz);
|
|
expect(next).not.toBeNull();
|
|
expect(next as number).toBeGreaterThan(t);
|
|
}
|
|
),
|
|
{ numRuns: 50 }
|
|
);
|
|
});
|
|
});
|
|
|
|
describe("property — nextRuns strictly monotonic", () => {
|
|
it("every adjacent pair is strictly ascending", { timeout: 30000 }, () => {
|
|
fc.assert(
|
|
fc.property(
|
|
fc.constantFrom(...EXPRESSIONS),
|
|
timestampArb,
|
|
fc.constantFrom(...TIMEZONES),
|
|
(e, t, tz) => {
|
|
const runs = parseCron(e).nextRuns(10, t, tz);
|
|
expect(runs.length).toBeGreaterThan(0);
|
|
for (let i = 1; i < runs.length; i += 1) {
|
|
expect(runs[i]).toBeGreaterThan(runs[i - 1] ?? 0);
|
|
}
|
|
}
|
|
),
|
|
{ numRuns: 30 }
|
|
);
|
|
});
|
|
});
|
|
|
|
describe("property — matches(nextRun) is true", () => {
|
|
it("the returned instant always matches the expression", {
|
|
timeout: 30000,
|
|
}, () => {
|
|
fc.assert(
|
|
fc.property(
|
|
fc.constantFrom(...EXPRESSIONS),
|
|
timestampArb,
|
|
fc.constantFrom(...TIMEZONES),
|
|
(e, t, tz) => {
|
|
const expr = parseCron(e);
|
|
const next = expr.nextRun(t, tz);
|
|
expect(expr.matches(next as number, tz)).toBe(true);
|
|
}
|
|
),
|
|
{ numRuns: 30 }
|
|
);
|
|
});
|
|
});
|
|
|
|
describe("property — previousRun(nextRun) <= after", () => {
|
|
it("backward search lands at-or-before the original anchor", {
|
|
timeout: 30000,
|
|
}, () => {
|
|
fc.assert(
|
|
fc.property(
|
|
fc.constantFrom(...EXPRESSIONS),
|
|
timestampArb,
|
|
fc.constantFrom(...TIMEZONES),
|
|
(e, t, tz) => {
|
|
const expr = parseCron(e);
|
|
const next = expr.nextRun(t, tz) as number;
|
|
const prev = expr.previousRun(next, tz);
|
|
expect(prev).not.toBeNull();
|
|
expect(prev as number).toBeLessThanOrEqual(t);
|
|
}
|
|
),
|
|
{ numRuns: 30 }
|
|
);
|
|
});
|
|
});
|