67 lines
3.5 KiB
TypeScript
67 lines
3.5 KiB
TypeScript
/** The five Ultimate calendar cases execute inside deno_core's V8. */
|
|
import {describe, expect, it} from "./automations-test-support";
|
|
const {describeSchedule,nextAt,parseSchedule}=await import(`${globalThis.automationBase}/automations/schedule.ts`);
|
|
const time = Date.parse('2026-01-05T08:00:00Z')
|
|
const next = (expr: string, tz = 'UTC') =>
|
|
new Date(nextAt(parseSchedule(`cron "${expr}" ${tz}`), time)).toISOString()
|
|
describe('automation schedules', () => {
|
|
it('matches fields, lists, ranges, steps, Sunday 7 and timezones', () => {
|
|
expect(next('30 9 5 1 *')).toBe('2026-01-05T09:30:00.000Z')
|
|
expect(next('5,10 8-10 * * 1-5')).toBe('2026-01-05T08:05:00.000Z')
|
|
expect(next('*/15 * * * *')).toBe('2026-01-05T08:15:00.000Z')
|
|
expect(next('10-30/10 * * * *')).toBe('2026-01-05T08:10:00.000Z')
|
|
expect(next('0 9 * * 7')).toBe('2026-01-11T09:00:00.000Z')
|
|
expect(next('0 10 * * 1-5', 'Europe/Vienna')).toBe('2026-01-05T09:00:00.000Z')
|
|
expect(next('0 8 * * *')).toBe('2026-01-06T08:00:00.000Z')
|
|
expect(next('0 9 10 * 1')).toBe('2026-01-05T09:00:00.000Z')
|
|
expect(next('0 9 */2 * *')).toBe('2026-01-05T09:00:00.000Z')
|
|
expect(next('0 9 */3 * *')).toBe('2026-01-07T09:00:00.000Z')
|
|
})
|
|
it('jumps sparse dates, rejects impossible ones and finds leap days', () => {
|
|
const yearly = parseSchedule('cron "0 0 1 1 *" Europe/Vienna')
|
|
const impossible = parseSchedule('cron "0 0 31 4 *" Europe/Vienna')
|
|
expect(new Date(nextAt(yearly, time)).toISOString()).toBe('2026-12-31T23:00:00.000Z')
|
|
expect(() => nextAt(impossible, time)).toThrow('8 years')
|
|
expect(nextAt(parseSchedule('cron "0 0 29 2 *" UTC'), time)).toBe(
|
|
Date.parse('2028-02-29T00:00:00Z'),
|
|
)
|
|
})
|
|
it('fires once in the repeated hour and skips nonexistent spring minutes', () => {
|
|
const schedule = parseSchedule('cron "30 2 * * *" Europe/Vienna')
|
|
const first = nextAt(schedule, Date.parse('2026-10-24T23:00:00Z'))
|
|
expect(first).toBe(Date.parse('2026-10-25T00:30:00Z'))
|
|
expect(nextAt(schedule, first)).toBe(Date.parse('2026-10-26T01:30:00Z'))
|
|
expect(nextAt(schedule, Date.parse('2026-03-28T23:00:00Z'))).toBe(
|
|
Date.parse('2026-03-30T00:30:00Z'),
|
|
)
|
|
})
|
|
it('parses relative, at and interval schedules and describes them', () => {
|
|
expect(parseSchedule('in 30m', time)).toEqual({ kind: 'at', time: '2026-01-05T08:30:00.000Z' })
|
|
expect(parseSchedule('in 2h', time)).toEqual({ kind: 'at', time: '2026-01-05T10:00:00.000Z' })
|
|
expect(parseSchedule('in 1d', time)).toEqual({ kind: 'at', time: '2026-01-06T08:00:00.000Z' })
|
|
expect(nextAt(parseSchedule('every 2s'), time)).toBe(time + 2000)
|
|
expect(describeSchedule(parseSchedule('every 15m'))).toBe('Every 15 minutes')
|
|
expect(describeSchedule(parseSchedule('every 1h'))).toBe('Every 1 hour')
|
|
expect(describeSchedule(parseSchedule('in 30m', time))).toContain('Once at')
|
|
expect(describeSchedule(parseSchedule('cron "0 9 * * 1-5" Europe/Vienna'))).toContain(
|
|
'Europe/Vienna',
|
|
)
|
|
expect(parseSchedule('cron "* * * * *"').kind).toBe('cron')
|
|
})
|
|
it('rejects invalid input and impossible cron dates', () => {
|
|
for (const input of [
|
|
'every 0.5s',
|
|
'every nope',
|
|
'in nope',
|
|
'at nope',
|
|
'cron "60 * * * *"',
|
|
'cron "*/0 * * * *"',
|
|
'cron "0 9 * *"',
|
|
'cron "* * * * *" Mars',
|
|
'cron "0 9 * * 5-1"',
|
|
])
|
|
expect(() => parseSchedule(input)).toThrow(/.+/)
|
|
expect(() => nextAt(parseSchedule('cron "0 9 31 2 *" UTC'), time)).toThrow('8 years')
|
|
})
|
|
})
|