mission-control/src/lib/__tests__/cron-utils.test.ts

124 lines
4.0 KiB
TypeScript

import { describe, it, expect } from 'vitest'
import { describeCronFrequency, validateCronExpression, generateCloneName } from '../cron-utils'
describe('describeCronFrequency', () => {
it('returns "every minute" for * * * * *', () => {
expect(describeCronFrequency('* * * * *')).toBe('every minute')
})
it('returns "every 5m" for */5 * * * *', () => {
expect(describeCronFrequency('*/5 * * * *')).toBe('every 5m')
})
it('returns "hourly at :00" for 0 * * * *', () => {
expect(describeCronFrequency('0 * * * *')).toBe('hourly at :00')
})
it('returns "daily at 14:30" for 30 14 * * *', () => {
expect(describeCronFrequency('30 14 * * *')).toBe('daily at 14:30')
})
it('returns "monthly" for 0 0 1 * *', () => {
expect(describeCronFrequency('0 0 1 * *')).toBe('monthly')
})
it('returns time with select days for 0 0 * * 1 (specific hour+dow)', () => {
expect(describeCronFrequency('0 0 * * 1')).toBe('00:00 (select days)')
})
it('returns "weekly" when dow is set and no earlier branch matches', () => {
expect(describeCronFrequency('1-30 * * * 1')).toBe('weekly')
})
it('falls through to raw schedule for */10 */2 * * *', () => {
expect(describeCronFrequency('*/10 */2 * * *')).toBe('*/10 */2 * * *')
})
it('returns "every 2h" for 0 */2 * * *', () => {
expect(describeCronFrequency('0 */2 * * *')).toBe('every 2h')
})
it('returns the raw schedule for non-standard expressions', () => {
expect(describeCronFrequency('invalid')).toBe('invalid')
})
it('strips trailing parenthetical comments', () => {
expect(describeCronFrequency('*/5 * * * * (some note)')).toBe('every 5m')
})
it('handles time with select days', () => {
expect(describeCronFrequency('0 9 * * 1,3,5')).toBe('09:00 (select days)')
})
})
describe('validateCronExpression', () => {
it('returns null for valid expression * * * * *', () => {
expect(validateCronExpression('* * * * *')).toBeNull()
})
it('returns null for valid step expression */5 * * * *', () => {
expect(validateCronExpression('*/5 * * * *')).toBeNull()
})
it('returns error for minute > 59', () => {
const result = validateCronExpression('60 * * * *')
expect(result).not.toBeNull()
expect(result).toContain('minute')
})
it('returns error for hour > 23', () => {
const result = validateCronExpression('* 25 * * *')
expect(result).not.toBeNull()
expect(result).toContain('hour')
})
it('returns error for wrong number of fields', () => {
const result = validateCronExpression('invalid')
expect(result).not.toBeNull()
expect(result).toContain('Expected 5 fields')
})
it('returns error for empty string', () => {
const result = validateCronExpression('')
expect(result).not.toBeNull()
})
it('returns null for valid range expression 1-5 * * * *', () => {
expect(validateCronExpression('1-5 * * * *')).toBeNull()
})
it('returns null for valid comma-separated values', () => {
expect(validateCronExpression('0,15,30,45 * * * *')).toBeNull()
})
it('returns error for day-of-month 0', () => {
const result = validateCronExpression('0 0 0 * *')
expect(result).not.toBeNull()
expect(result).toContain('day of month')
})
it('returns error for month > 12', () => {
const result = validateCronExpression('0 0 1 13 *')
expect(result).not.toBeNull()
expect(result).toContain('month')
})
})
describe('generateCloneName', () => {
it('returns "Name (copy)" when no conflicts', () => {
expect(generateCloneName('My Job', [])).toBe('My Job (copy)')
})
it('returns "Name (copy 2)" when "(copy)" exists', () => {
expect(generateCloneName('My Job', ['My Job (copy)'])).toBe('My Job (copy 2)')
})
it('returns "Name (copy 3)" when "(copy)" and "(copy 2)" exist', () => {
expect(generateCloneName('My Job', ['My Job (copy)', 'My Job (copy 2)'])).toBe('My Job (copy 3)')
})
it('is case-insensitive when checking existing names', () => {
expect(generateCloneName('My Job', ['my job (copy)'])).toBe('My Job (copy 2)')
})
})