@@ -11,6 +11,7 @@ import {
1111 getSubBlockValue ,
1212 parseCronToHumanReadable ,
1313 parseTimeString ,
14+ validateCronExpression ,
1415} from '@/lib/schedules/utils'
1516
1617describe ( 'Schedule Utilities' , ( ) => {
@@ -102,7 +103,7 @@ describe('Schedule Utilities', () => {
102103 weeklyTime : [ 12 , 0 ] ,
103104 monthlyDay : 15 ,
104105 monthlyTime : [ 14 , 30 ] ,
105- cronExpression : '' ,
106+ cronExpression : null ,
106107 } )
107108 } )
108109
@@ -128,7 +129,7 @@ describe('Schedule Utilities', () => {
128129 weeklyTime : [ 9 , 0 ] , // Default
129130 monthlyDay : 1 , // Default
130131 monthlyTime : [ 9 , 0 ] , // Default
131- cronExpression : '' ,
132+ cronExpression : null ,
132133 } )
133134 } )
134135 } )
@@ -145,7 +146,7 @@ describe('Schedule Utilities', () => {
145146 monthlyDay : 15 ,
146147 monthlyTime : [ 14 , 30 ] as [ number , number ] ,
147148 timezone : 'UTC' ,
148- cronExpression : '' ,
149+ cronExpression : null ,
149150 }
150151
151152 // Minutes (every 15 minutes)
@@ -199,7 +200,7 @@ describe('Schedule Utilities', () => {
199200 monthlyDay : 15 ,
200201 monthlyTime : [ 14 , 30 ] as [ number , number ] ,
201202 timezone : 'UTC' ,
202- cronExpression : '' ,
203+ cronExpression : null ,
203204 }
204205
205206 expect ( generateCronExpression ( 'minutes' , standardScheduleValues ) ) . toBe ( '*/15 * * * *' )
@@ -234,7 +235,7 @@ describe('Schedule Utilities', () => {
234235 weeklyTime : [ 9 , 0 ] as [ number , number ] ,
235236 monthlyDay : 1 ,
236237 monthlyTime : [ 9 , 0 ] as [ number , number ] ,
237- cronExpression : '' ,
238+ cronExpression : null ,
238239 }
239240
240241 const nextRun = calculateNextRunTime ( 'minutes' , scheduleValues )
@@ -259,7 +260,7 @@ describe('Schedule Utilities', () => {
259260 weeklyTime : [ 9 , 0 ] as [ number , number ] ,
260261 monthlyDay : 1 ,
261262 monthlyTime : [ 9 , 0 ] as [ number , number ] ,
262- cronExpression : '' ,
263+ cronExpression : null ,
263264 }
264265
265266 const nextRun = calculateNextRunTime ( 'minutes' , scheduleValues )
@@ -281,7 +282,7 @@ describe('Schedule Utilities', () => {
281282 weeklyTime : [ 9 , 0 ] as [ number , number ] ,
282283 monthlyDay : 1 ,
283284 monthlyTime : [ 9 , 0 ] as [ number , number ] ,
284- cronExpression : '' ,
285+ cronExpression : null ,
285286 }
286287
287288 const nextRun = calculateNextRunTime ( 'hourly' , scheduleValues )
@@ -304,7 +305,7 @@ describe('Schedule Utilities', () => {
304305 weeklyTime : [ 9 , 0 ] as [ number , number ] ,
305306 monthlyDay : 1 ,
306307 monthlyTime : [ 9 , 0 ] as [ number , number ] ,
307- cronExpression : '' ,
308+ cronExpression : null ,
308309 }
309310
310311 const nextRun = calculateNextRunTime ( 'daily' , scheduleValues )
@@ -328,7 +329,7 @@ describe('Schedule Utilities', () => {
328329 weeklyTime : [ 10 , 0 ] as [ number , number ] ,
329330 monthlyDay : 1 ,
330331 monthlyTime : [ 9 , 0 ] as [ number , number ] ,
331- cronExpression : '' ,
332+ cronExpression : null ,
332333 }
333334
334335 const nextRun = calculateNextRunTime ( 'weekly' , scheduleValues )
@@ -351,7 +352,7 @@ describe('Schedule Utilities', () => {
351352 weeklyTime : [ 9 , 0 ] as [ number , number ] ,
352353 monthlyDay : 15 ,
353354 monthlyTime : [ 14 , 30 ] as [ number , number ] ,
354- cronExpression : '' ,
355+ cronExpression : null ,
355356 }
356357
357358 const nextRun = calculateNextRunTime ( 'monthly' , scheduleValues )
@@ -376,7 +377,7 @@ describe('Schedule Utilities', () => {
376377 weeklyTime : [ 9 , 0 ] as [ number , number ] ,
377378 monthlyDay : 1 ,
378379 monthlyTime : [ 9 , 0 ] as [ number , number ] ,
379- cronExpression : '' ,
380+ cronExpression : null ,
380381 }
381382
382383 // Last ran 10 minutes ago
@@ -404,7 +405,7 @@ describe('Schedule Utilities', () => {
404405 weeklyTime : [ 9 , 0 ] as [ number , number ] ,
405406 monthlyDay : 1 ,
406407 monthlyTime : [ 9 , 0 ] as [ number , number ] ,
407- cronExpression : '' ,
408+ cronExpression : null ,
408409 }
409410
410411 const nextRun = calculateNextRunTime ( 'minutes' , scheduleValues )
@@ -425,7 +426,7 @@ describe('Schedule Utilities', () => {
425426 weeklyTime : [ 9 , 0 ] as [ number , number ] ,
426427 monthlyDay : 1 ,
427428 monthlyTime : [ 9 , 0 ] as [ number , number ] ,
428- cronExpression : '' ,
429+ cronExpression : null ,
429430 }
430431
431432 const nextRun = calculateNextRunTime ( 'minutes' , scheduleValues )
@@ -436,6 +437,50 @@ describe('Schedule Utilities', () => {
436437 } )
437438 } )
438439
440+ describe ( 'validateCronExpression' , ( ) => {
441+ it . concurrent ( 'should validate correct cron expressions' , ( ) => {
442+ expect ( validateCronExpression ( '0 9 * * *' ) ) . toEqual ( {
443+ isValid : true ,
444+ nextRun : expect . any ( Date ) ,
445+ } )
446+ expect ( validateCronExpression ( '*/15 * * * *' ) ) . toEqual ( {
447+ isValid : true ,
448+ nextRun : expect . any ( Date ) ,
449+ } )
450+ expect ( validateCronExpression ( '30 14 15 * *' ) ) . toEqual ( {
451+ isValid : true ,
452+ nextRun : expect . any ( Date ) ,
453+ } )
454+ } )
455+
456+ it . concurrent ( 'should reject invalid cron expressions' , ( ) => {
457+ expect ( validateCronExpression ( 'invalid' ) ) . toEqual ( {
458+ isValid : false ,
459+ error : expect . stringContaining ( 'invalid' ) ,
460+ } )
461+ expect ( validateCronExpression ( '60 * * * *' ) ) . toEqual ( {
462+ isValid : false ,
463+ error : expect . any ( String ) ,
464+ } )
465+ expect ( validateCronExpression ( '' ) ) . toEqual ( {
466+ isValid : false ,
467+ error : 'Cron expression cannot be empty' ,
468+ } )
469+ expect ( validateCronExpression ( ' ' ) ) . toEqual ( {
470+ isValid : false ,
471+ error : 'Cron expression cannot be empty' ,
472+ } )
473+ } )
474+
475+ it . concurrent ( 'should detect impossible cron expressions' , ( ) => {
476+ // This would be February 31st - impossible date
477+ expect ( validateCronExpression ( '0 0 31 2 *' ) ) . toEqual ( {
478+ isValid : false ,
479+ error : 'Cron expression produces no future occurrences' ,
480+ } )
481+ } )
482+ } )
483+
439484 describe ( 'parseCronToHumanReadable' , ( ) => {
440485 it . concurrent ( 'should parse common cron patterns' , ( ) => {
441486 expect ( parseCronToHumanReadable ( '* * * * *' ) ) . toBe ( 'Every minute' )
0 commit comments