mirror of
https://github.com/langgenius/dify.git
synced 2026-01-23 20:22:13 +08:00
234 lines
8.4 KiB
TypeScript
234 lines
8.4 KiB
TypeScript
import { isValidCronExpression, parseCronExpression } from './cron-parser'
|
|
|
|
describe('cron-parser', () => {
|
|
describe('isValidCronExpression', () => {
|
|
test('validates correct cron expressions', () => {
|
|
expect(isValidCronExpression('15 10 1 * *')).toBe(true)
|
|
expect(isValidCronExpression('0 0 * * 0')).toBe(true)
|
|
expect(isValidCronExpression('*/5 * * * *')).toBe(true)
|
|
expect(isValidCronExpression('0 9-17 * * 1-5')).toBe(true)
|
|
expect(isValidCronExpression('30 14 * * 1')).toBe(true)
|
|
expect(isValidCronExpression('0 0 1,15 * *')).toBe(true)
|
|
})
|
|
|
|
test('rejects invalid cron expressions', () => {
|
|
expect(isValidCronExpression('')).toBe(false)
|
|
expect(isValidCronExpression('15 10 1')).toBe(false) // Not enough fields
|
|
expect(isValidCronExpression('15 10 1 * * *')).toBe(false) // Too many fields
|
|
expect(isValidCronExpression('60 10 1 * *')).toBe(false) // Invalid minute
|
|
expect(isValidCronExpression('15 25 1 * *')).toBe(false) // Invalid hour
|
|
expect(isValidCronExpression('15 10 32 * *')).toBe(false) // Invalid day
|
|
expect(isValidCronExpression('15 10 1 13 *')).toBe(false) // Invalid month
|
|
expect(isValidCronExpression('15 10 1 * 7')).toBe(false) // Invalid day of week
|
|
})
|
|
|
|
test('handles edge cases', () => {
|
|
expect(isValidCronExpression(' 15 10 1 * * ')).toBe(true) // Whitespace
|
|
expect(isValidCronExpression('0 0 29 2 *')).toBe(true) // Feb 29 (valid in leap years)
|
|
expect(isValidCronExpression('59 23 31 12 6')).toBe(true) // Max values
|
|
})
|
|
})
|
|
|
|
describe('parseCronExpression', () => {
|
|
beforeEach(() => {
|
|
// Mock current time to make tests deterministic
|
|
jest.useFakeTimers()
|
|
jest.setSystemTime(new Date('2024-01-15T10:00:00Z'))
|
|
})
|
|
|
|
afterEach(() => {
|
|
jest.useRealTimers()
|
|
})
|
|
|
|
test('parses monthly expressions correctly', () => {
|
|
const result = parseCronExpression('15 10 1 * *') // 1st day of every month at 10:15
|
|
|
|
expect(result).toHaveLength(5)
|
|
expect(result[0].getDate()).toBe(1) // February 1st
|
|
expect(result[0].getHours()).toBe(10)
|
|
expect(result[0].getMinutes()).toBe(15)
|
|
expect(result[1].getDate()).toBe(1) // March 1st
|
|
expect(result[2].getDate()).toBe(1) // April 1st
|
|
})
|
|
|
|
test('parses weekly expressions correctly', () => {
|
|
const result = parseCronExpression('30 14 * * 1') // Every Monday at 14:30
|
|
|
|
expect(result).toHaveLength(5)
|
|
// Should find next 5 Mondays
|
|
result.forEach((date) => {
|
|
expect(date.getDay()).toBe(1) // Monday
|
|
expect(date.getHours()).toBe(14)
|
|
expect(date.getMinutes()).toBe(30)
|
|
})
|
|
})
|
|
|
|
test('parses daily expressions correctly', () => {
|
|
const result = parseCronExpression('0 9 * * *') // Every day at 9:00
|
|
|
|
expect(result).toHaveLength(5)
|
|
result.forEach((date) => {
|
|
expect(date.getHours()).toBe(9)
|
|
expect(date.getMinutes()).toBe(0)
|
|
})
|
|
|
|
// Should be consecutive days (starting from tomorrow since current time is 10:00)
|
|
for (let i = 1; i < result.length; i++) {
|
|
const prevDate = new Date(result[i - 1])
|
|
const currDate = new Date(result[i])
|
|
const dayDiff = (currDate.getTime() - prevDate.getTime()) / (1000 * 60 * 60 * 24)
|
|
expect(dayDiff).toBe(1)
|
|
}
|
|
})
|
|
|
|
test('handles complex cron expressions with ranges', () => {
|
|
const result = parseCronExpression('0 9-17 * * 1-5') // Weekdays, 9-17 hours
|
|
|
|
expect(result).toHaveLength(5)
|
|
result.forEach((date) => {
|
|
expect(date.getDay()).toBeGreaterThanOrEqual(1) // Monday
|
|
expect(date.getDay()).toBeLessThanOrEqual(5) // Friday
|
|
expect(date.getHours()).toBeGreaterThanOrEqual(9)
|
|
expect(date.getHours()).toBeLessThanOrEqual(17)
|
|
expect(date.getMinutes()).toBe(0)
|
|
})
|
|
})
|
|
|
|
test('handles step expressions', () => {
|
|
const result = parseCronExpression('*/15 * * * *') // Every 15 minutes
|
|
|
|
expect(result).toHaveLength(5)
|
|
result.forEach((date) => {
|
|
expect(date.getMinutes() % 15).toBe(0)
|
|
})
|
|
})
|
|
|
|
test('handles list expressions', () => {
|
|
const result = parseCronExpression('0 0 1,15 * *') // 1st and 15th of each month
|
|
|
|
expect(result).toHaveLength(5)
|
|
result.forEach((date) => {
|
|
expect([1, 15]).toContain(date.getDate())
|
|
expect(date.getHours()).toBe(0)
|
|
expect(date.getMinutes()).toBe(0)
|
|
})
|
|
})
|
|
|
|
test('handles expressions that span multiple months', () => {
|
|
// Test with an expression that might not have many matches in current month
|
|
const result = parseCronExpression('0 12 29 * *') // 29th of each month at noon
|
|
|
|
expect(result.length).toBeGreaterThan(0)
|
|
expect(result.length).toBeLessThanOrEqual(5)
|
|
result.forEach((date) => {
|
|
expect(date.getDate()).toBe(29)
|
|
expect(date.getHours()).toBe(12)
|
|
expect(date.getMinutes()).toBe(0)
|
|
})
|
|
})
|
|
|
|
test('returns empty array for invalid expressions', () => {
|
|
expect(parseCronExpression('')).toEqual([])
|
|
expect(parseCronExpression('invalid')).toEqual([])
|
|
expect(parseCronExpression('60 10 1 * *')).toEqual([])
|
|
expect(parseCronExpression('15 25 1 * *')).toEqual([])
|
|
})
|
|
|
|
test('handles edge case: February 29th in non-leap years', () => {
|
|
// Set to a non-leap year
|
|
jest.setSystemTime(new Date('2023-01-15T10:00:00Z'))
|
|
|
|
const result = parseCronExpression('0 12 29 2 *') // Feb 29th at noon
|
|
|
|
// Should return empty or skip 2023 and find 2024
|
|
if (result.length > 0) {
|
|
result.forEach((date) => {
|
|
expect(date.getMonth()).toBe(1) // February
|
|
expect(date.getDate()).toBe(29)
|
|
// Should be in a leap year
|
|
const year = date.getFullYear()
|
|
expect(year % 4).toBe(0)
|
|
})
|
|
}
|
|
})
|
|
|
|
test('sorts results chronologically', () => {
|
|
const result = parseCronExpression('0 */6 * * *') // Every 6 hours
|
|
|
|
expect(result).toHaveLength(5)
|
|
for (let i = 1; i < result.length; i++)
|
|
expect(result[i].getTime()).toBeGreaterThan(result[i - 1].getTime())
|
|
})
|
|
|
|
test('excludes past times', () => {
|
|
// Set current time to 15:30
|
|
jest.setSystemTime(new Date('2024-01-15T15:30:00Z'))
|
|
|
|
const result = parseCronExpression('0 10 * * *') // Daily at 10:00
|
|
|
|
expect(result).toHaveLength(5)
|
|
result.forEach((date) => {
|
|
expect(date.getTime()).toBeGreaterThan(Date.now())
|
|
})
|
|
|
|
// First result should be tomorrow since today's 10:00 has passed
|
|
expect(result[0].getDate()).toBe(16)
|
|
})
|
|
|
|
test('handles midnight expressions correctly', () => {
|
|
const result = parseCronExpression('0 0 * * *') // Daily at midnight
|
|
|
|
expect(result).toHaveLength(5)
|
|
result.forEach((date) => {
|
|
expect(date.getHours()).toBe(0)
|
|
expect(date.getMinutes()).toBe(0)
|
|
})
|
|
})
|
|
|
|
test('handles year boundary correctly', () => {
|
|
// Set to end of December
|
|
jest.setSystemTime(new Date('2024-12-30T10:00:00Z'))
|
|
|
|
const result = parseCronExpression('0 12 1 * *') // 1st of every month at noon
|
|
|
|
expect(result).toHaveLength(5)
|
|
// Should include January 1st of next year
|
|
const nextYear = result.find(date => date.getFullYear() === 2025)
|
|
expect(nextYear).toBeDefined()
|
|
if (nextYear) {
|
|
expect(nextYear.getMonth()).toBe(0) // January
|
|
expect(nextYear.getDate()).toBe(1)
|
|
}
|
|
})
|
|
})
|
|
|
|
describe('performance tests', () => {
|
|
test('performs well for complex expressions', () => {
|
|
const start = performance.now()
|
|
|
|
// Test multiple complex expressions
|
|
const expressions = [
|
|
'*/5 9-17 * * 1-5', // Every 5 minutes, weekdays, business hours
|
|
'0 */2 1,15 * *', // Every 2 hours on 1st and 15th
|
|
'30 14 * * 1,3,5', // Mon, Wed, Fri at 14:30
|
|
'15,45 8-18 * * 1-5', // 15 and 45 minutes past the hour, weekdays
|
|
]
|
|
|
|
expressions.forEach((expr) => {
|
|
const result = parseCronExpression(expr)
|
|
expect(result).toHaveLength(5)
|
|
})
|
|
|
|
// Test quarterly expression separately (may return fewer than 5 results)
|
|
const quarterlyResult = parseCronExpression('0 0 1 */3 *') // First day of every 3rd month
|
|
expect(quarterlyResult.length).toBeGreaterThan(0)
|
|
expect(quarterlyResult.length).toBeLessThanOrEqual(5)
|
|
|
|
const end = performance.now()
|
|
|
|
// Should complete within reasonable time (less than 100ms for all expressions)
|
|
expect(end - start).toBeLessThan(100)
|
|
})
|
|
})
|
|
})
|