Initial Commit
This commit is contained in:
111
packages/foundation/timing/cron/tests/property.test.ts
Normal file
111
packages/foundation/timing/cron/tests/property.test.ts
Normal file
@@ -0,0 +1,111 @@
|
||||
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 }
|
||||
);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user