cron.test.ts•6.15 kB
import { describe, it, expect } from 'vitest';
// TODO: think about where to put tests for shared utils
import { calculateNextRun, validateCronExpression } from '@superglue/shared';
describe('calculateNextRun', () => {
it('should calculate next run for every minute cron expression', () => {
const cronExpression = '* * * * *';
const from = new Date('2024-01-01T12:00:00Z');
const nextRun = calculateNextRun(cronExpression, 'UTC', from);
expect(nextRun).toEqual(new Date('2024-01-01T12:01:00Z'));
});
it('should calculate next run for daily at midnight', () => {
const cronExpression = '0 0 * * *';
const from = new Date('2024-01-01T12:30:00Z');
const nextRun = calculateNextRun(cronExpression, 'UTC', from);
expect(nextRun).toEqual(new Date('2024-01-02T00:00:00Z'));
});
it('should calculate next run for weekly on Monday at 9 AM', () => {
const cronExpression = '0 9 * * 1';
const from = new Date('2024-01-01T12:00:00Z');
const nextRun = calculateNextRun(cronExpression, 'UTC', from);
expect(nextRun).toEqual(new Date('2024-01-08T09:00:00Z'));
});
it('should use current time when from is not provided', () => {
const cronExpression = '* * * * *';
const before = new Date();
const nextRun = calculateNextRun(cronExpression, 'UTC');
const after = new Date();
expect(nextRun.getTime()).toBeGreaterThan(before.getTime());
expect(nextRun.getTime()).toBeLessThan(after.getTime() + 60000);
});
it('should handle complex cron expression', () => {
const cronExpression = '30 14 * * 1-5';
const from = new Date('2024-01-01T10:00:00Z');
const nextRun = calculateNextRun(cronExpression, 'UTC', from);
expect(nextRun).toEqual(new Date('2024-01-01T14:30:00Z'));
});
it('should throw error for invalid cron expression', () => {
const invalidCron = 'invalid cron';
expect(() => calculateNextRun(invalidCron, 'UTC')).toThrow();
});
it('should calculate next run for cron expression in a non-UTC timezone', () => {
// It's currently 00:00 UTC, which is 8:00 AM in Asia/Shanghai. The cron is set to run at 9:00 AM local time, which means it should execute at 01:00 UTC.
const cronExpression = '0 9 * * *'; // local time
const from = new Date('2024-02-02T00:00:00Z'); // utc time
const nextRun = calculateNextRun(cronExpression, 'Asia/Shanghai', from);
expect(nextRun.toISOString()).toEqual('2024-02-02T01:00:00.000Z');
});
});
describe('validateCron', () => {
it('should accept valid basic cron expressions', () => {
expect(validateCronExpression('*/2 * * * *')).toBe(true); // Every 2 minutes
expect(validateCronExpression('*/15 * * * *')).toBe(true); // Every 15 minutes
expect(validateCronExpression('*/30 * * * *')).toBe(true); // Every 30 minutes
expect(validateCronExpression('0 * * * *')).toBe(true); // Hourly
expect(validateCronExpression('0 0 * * *')).toBe(true); // Daily
expect(validateCronExpression('0 0 * * 0')).toBe(true); // Weekly
expect(validateCronExpression('0 0 1 * *')).toBe(true); // Monthly
})
it('should accept cron expressions with stepping', () => {
expect(validateCronExpression('*/15 * * * *')).toBe(true); // Every 15 minutes
});
it('should reject invalid cron expression', () => {
expect(validateCronExpression('invalid cron')).toBe(false);
});
it('should reject cron expression with seconds', () => {
expect(validateCronExpression('*/10 * * * * *')).toBe(false); // Every 10 seconds
});
it('should reject cron expression with years', () => {
expect(validateCronExpression('0 0 1 1 * 2024')).toBe(false); // At 12:00 AM, on day 1 of the month, only in January, only in 2024
});
it('should reject cron expression with aliases', () => {
expect(validateCronExpression('@hourly')).toBe(false);
expect(validateCronExpression('@daily')).toBe(false);
expect(validateCronExpression('@weekly')).toBe(false);
expect(validateCronExpression('@monthly')).toBe(false);
expect(validateCronExpression('@yearly')).toBe(false);
expect(validateCronExpression('@reboot')).toBe(false);
});
it('should reject cron expressions with blank day field', () => {
expect(validateCronExpression('0 9 15 * ?')).toBe(false); // At 09:00 AM, on day 15 of the month
});
it('should reject cron expressions with last day of month', () => {
expect(validateCronExpression('0 9 L * *')).toBe(false); // Last day of month
expect(validateCronExpression('0 9 L-2 * *')).toBe(false); // 2nd to last day
expect(validateCronExpression('0 9 L-5 * *')).toBe(false); // 5th to last day
});
it('should reject cron expressions with last day of week', () => {
expect(validateCronExpression('0 9 * * 5L')).toBe(false); // Last Friday
expect(validateCronExpression('0 9 * * 1L')).toBe(false); // Last Monday
expect(validateCronExpression('0 9 * * 0L')).toBe(false); // Last Sunday
});
it('should reject cron expressions with nearest weekday', () => {
expect(validateCronExpression('0 9 15W * *')).toBe(false); // Weekday closest to 15th
expect(validateCronExpression('0 9 1W * *')).toBe(false); // Weekday closest to 1st
expect(validateCronExpression('0 9 LW * *')).toBe(false); // Last weekday of month
});
it('should reject cron expressions with nth weekday of month', () => {
expect(validateCronExpression('0 9 * * 6#3')).toBe(false); // 3rd Friday
expect(validateCronExpression('0 9 * * 1#1')).toBe(false); // 1st Monday
expect(validateCronExpression('0 9 * * 5#4')).toBe(false); // 4th Friday
expect(validateCronExpression('0 9 * * 0#2')).toBe(false); // 2nd Sunday
});
});