github.com/cozy/cozy-stack@v0.0.0-20240603063001-31110fa4cae1/model/job/periodic_parser_test.go (about) 1 package job_test 2 3 import ( 4 "fmt" 5 "strconv" 6 "strings" 7 "testing" 8 "time" 9 10 "github.com/cozy/cozy-stack/model/job" 11 "github.com/robfig/cron/v3" 12 "github.com/stretchr/testify/assert" 13 "github.com/stretchr/testify/require" 14 ) 15 16 func TestPeriodicParser(t *testing.T) { 17 p := job.NewPeriodicParser() 18 19 spec, err := p.Parse(job.WeeklyKind, "") 20 require.NoError(t, err) 21 assert.Equal(t, spec.DaysOfWeek, []int{0, 1, 2, 3, 4, 5, 6}) 22 assert.Equal(t, spec.AfterHour, 0) 23 assert.Equal(t, spec.BeforeHour, 24) 24 25 spec, err = p.Parse(job.MonthlyKind, "") 26 require.NoError(t, err) 27 assert.Equal(t, spec.DaysOfMonth, []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28}) 28 assert.Equal(t, spec.AfterHour, 0) 29 assert.Equal(t, spec.BeforeHour, 24) 30 31 // Month days 32 spec, err = p.Parse(job.MonthlyKind, "on the 1") 33 require.NoError(t, err) 34 assert.Equal(t, spec.DaysOfMonth, []int{1}) 35 assert.Equal(t, spec.AfterHour, 0) 36 assert.Equal(t, spec.BeforeHour, 24) 37 38 spec, err = p.Parse(job.MonthlyKind, "on the 1,16") 39 require.NoError(t, err) 40 assert.Equal(t, spec.DaysOfMonth, []int{1, 16}) 41 assert.Equal(t, spec.AfterHour, 0) 42 assert.Equal(t, spec.BeforeHour, 24) 43 44 spec, err = p.Parse(job.MonthlyKind, "on the 1-3,5-7,9-11") 45 require.NoError(t, err) 46 assert.Equal(t, spec.DaysOfMonth, []int{1, 2, 3, 5, 6, 7, 9, 10, 11}) 47 assert.Equal(t, spec.AfterHour, 0) 48 assert.Equal(t, spec.BeforeHour, 24) 49 50 // Week days 51 spec, err = p.Parse(job.WeeklyKind, "on monday") 52 require.NoError(t, err) 53 assert.Equal(t, spec.DaysOfWeek, []int{1}) 54 assert.Equal(t, spec.AfterHour, 0) 55 assert.Equal(t, spec.BeforeHour, 24) 56 57 spec, err = p.Parse(job.WeeklyKind, "on mon,wed,fri") 58 require.NoError(t, err) 59 assert.Equal(t, spec.DaysOfWeek, []int{1, 3, 5}) 60 assert.Equal(t, spec.AfterHour, 0) 61 assert.Equal(t, spec.BeforeHour, 24) 62 63 spec, err = p.Parse(job.WeeklyKind, "on wed-fri") 64 require.NoError(t, err) 65 assert.Equal(t, spec.DaysOfWeek, []int{3, 4, 5}) 66 assert.Equal(t, spec.AfterHour, 0) 67 assert.Equal(t, spec.BeforeHour, 24) 68 69 spec, err = p.Parse(job.WeeklyKind, "on fri-mon") 70 require.NoError(t, err) 71 assert.Equal(t, spec.DaysOfWeek, []int{5, 6, 0, 1}) 72 assert.Equal(t, spec.AfterHour, 0) 73 assert.Equal(t, spec.BeforeHour, 24) 74 75 spec, err = p.Parse(job.WeeklyKind, "on weekday") 76 require.NoError(t, err) 77 assert.Equal(t, spec.DaysOfWeek, []int{1, 2, 3, 4, 5}) 78 assert.Equal(t, spec.AfterHour, 0) 79 assert.Equal(t, spec.BeforeHour, 24) 80 81 spec, err = p.Parse(job.WeeklyKind, "on weekend") 82 require.NoError(t, err) 83 assert.Equal(t, spec.DaysOfWeek, []int{0, 6}) 84 assert.Equal(t, spec.AfterHour, 0) 85 assert.Equal(t, spec.BeforeHour, 24) 86 87 spec, err = p.Parse(job.WeeklyKind, "on weekday,saturday") 88 require.NoError(t, err) 89 assert.Equal(t, spec.DaysOfWeek, []int{1, 2, 3, 4, 5, 6}) 90 assert.Equal(t, spec.AfterHour, 0) 91 assert.Equal(t, spec.BeforeHour, 24) 92 93 // Hours 94 spec, err = p.Parse(job.DailyKind, "before 5am") 95 require.NoError(t, err) 96 assert.Equal(t, spec.AfterHour, 0) 97 assert.Equal(t, spec.BeforeHour, 5) 98 99 spec, err = p.Parse(job.DailyKind, "after 10pm") 100 require.NoError(t, err) 101 assert.Equal(t, spec.AfterHour, 22) 102 assert.Equal(t, spec.BeforeHour, 24) 103 104 spec, err = p.Parse(job.DailyKind, "between 8am and 6pm") 105 require.NoError(t, err) 106 assert.Equal(t, spec.AfterHour, 8) 107 assert.Equal(t, spec.BeforeHour, 18) 108 109 spec, err = p.Parse(job.WeeklyKind, "between 12am and 12pm") 110 require.NoError(t, err) 111 assert.Equal(t, spec.DaysOfWeek, []int{0, 1, 2, 3, 4, 5, 6}) 112 assert.Equal(t, spec.AfterHour, 0) 113 assert.Equal(t, spec.BeforeHour, 12) 114 115 spec, err = p.Parse(job.WeeklyKind, "on monday before 9am") 116 require.NoError(t, err) 117 assert.Equal(t, spec.DaysOfWeek, []int{1}) 118 assert.Equal(t, spec.AfterHour, 0) 119 assert.Equal(t, spec.BeforeHour, 9) 120 121 // Errors 122 _, err = p.Parse(job.DailyKind, "on monday") 123 assert.Error(t, err) 124 _, err = p.Parse(job.WeeklyKind, "xyz") 125 assert.Error(t, err) 126 _, err = p.Parse(job.WeeklyKind, "on") 127 assert.Error(t, err) 128 _, err = p.Parse(job.WeeklyKind, "on xyz") 129 assert.Error(t, err) 130 _, err = p.Parse(job.WeeklyKind, "on mon,") 131 assert.Error(t, err) 132 _, err = p.Parse(job.WeeklyKind, "on mon-") 133 assert.Error(t, err) 134 _, err = p.Parse(job.WeeklyKind, "on -mon") 135 assert.Error(t, err) 136 _, err = p.Parse(job.WeeklyKind, "on mon-weekend") 137 assert.Error(t, err) 138 _, err = p.Parse(job.WeeklyKind, "after") 139 assert.Error(t, err) 140 _, err = p.Parse(job.WeeklyKind, "after xyz") 141 assert.Error(t, err) 142 _, err = p.Parse(job.WeeklyKind, "before") 143 assert.Error(t, err) 144 _, err = p.Parse(job.WeeklyKind, "before xyz") 145 assert.Error(t, err) 146 _, err = p.Parse(job.WeeklyKind, "between 8am and 8am") 147 assert.Error(t, err) 148 _, err = p.Parse(job.WeeklyKind, "on the monday") 149 assert.Error(t, err) 150 _, err = p.Parse(job.MonthlyKind, "on the") 151 assert.Error(t, err) 152 _, err = p.Parse(job.MonthlyKind, "on the xyz") 153 assert.Error(t, err) 154 _, err = p.Parse(job.MonthlyKind, "on 1-") 155 assert.Error(t, err) 156 _, err = p.Parse(job.MonthlyKind, "on 1-5") 157 assert.Error(t, err) 158 _, err = p.Parse(job.MonthlyKind, "on the 5-1") 159 assert.Error(t, err) 160 _, err = p.Parse(job.MonthlyKind, "on the 5-5") 161 assert.Error(t, err) 162 163 // Not yet supported, but we may want it in the future 164 _, err = p.Parse(job.DailyKind, "on the 1-5") 165 assert.Error(t, err) 166 _, err = p.Parse(job.WeeklyKind, "between 9pm and 3am") 167 assert.Error(t, err) 168 _, err = p.Parse(job.WeeklyKind, "on the 1-15") 169 assert.Error(t, err) 170 _, err = p.Parse(job.MonthlyKind, "on monday") 171 assert.Error(t, err) 172 } 173 174 func TestToRandomCrontab(t *testing.T) { 175 day := 24 * time.Hour 176 cronParser := cron.NewParser(cron.SecondOptional | cron.Minute | cron.Hour | cron.Dom | cron.Month | cron.Dow) 177 seed := fmt.Sprintf("%d", time.Now().UnixNano()) 178 179 // Monthly 180 spec := job.PeriodicSpec{ 181 Frequency: job.MonthlyKind, 182 DaysOfMonth: []int{3, 4, 5, 6, 7}, 183 AfterHour: 8, 184 BeforeHour: 16, 185 } 186 crontab := spec.ToRandomCrontab(seed) 187 fields := strings.Fields(crontab) 188 require.Len(t, fields, 6) 189 190 second, err := strconv.Atoi(fields[0]) 191 assert.NoError(t, err) 192 assert.True(t, 0 <= second && second < 60) 193 194 minute, err := strconv.Atoi(fields[1]) 195 assert.NoError(t, err) 196 assert.True(t, 0 <= minute && minute < 60) 197 198 hour, err := strconv.Atoi(fields[2]) 199 assert.NoError(t, err) 200 assert.True(t, 8 <= hour && hour < 16) 201 202 dom, err := strconv.Atoi(fields[3]) // day of month 203 assert.NoError(t, err) 204 assert.True(t, 3 <= dom && dom <= 7) 205 206 assert.Equal(t, fields[4], "*") // month 207 assert.Equal(t, fields[5], "*") // day of week 208 209 // Check that two successive executions are separated by about 30 days 210 schedule, err := cronParser.Parse(crontab) 211 require.NoError(t, err) 212 exec := schedule.Next(time.Now()) 213 next := schedule.Next(exec) 214 assert.WithinDuration(t, exec.Add(30*day), next, 3*day) 215 216 // Weekly 217 spec = job.PeriodicSpec{ 218 Frequency: job.WeeklyKind, 219 DaysOfWeek: []int{1, 2, 3, 4, 5}, 220 AfterHour: 0, 221 BeforeHour: 5, 222 } 223 crontab = spec.ToRandomCrontab(seed) 224 fields = strings.Fields(crontab) 225 require.Len(t, fields, 6) 226 227 second, err = strconv.Atoi(fields[0]) 228 assert.NoError(t, err) 229 assert.True(t, 0 <= second && second < 60) 230 231 minute, err = strconv.Atoi(fields[1]) 232 assert.NoError(t, err) 233 assert.True(t, 0 <= minute && minute < 60) 234 235 hour, err = strconv.Atoi(fields[2]) 236 assert.NoError(t, err) 237 assert.True(t, 0 <= hour && hour < 5) 238 239 assert.Equal(t, fields[3], "*") // day of month 240 assert.Equal(t, fields[4], "*") // month 241 242 dow, err := strconv.Atoi(fields[5]) // day of week 243 assert.NoError(t, err) 244 assert.True(t, 1 <= dow && dow <= 5) 245 246 // Check that two successive executions are separated by about 7 days 247 schedule, err = cronParser.Parse(crontab) 248 require.NoError(t, err) 249 exec = schedule.Next(time.Now()) 250 next = schedule.Next(exec) 251 assert.WithinDuration(t, exec.Add(7*day), next, 3*time.Minute) 252 253 // Daily 254 spec = job.PeriodicSpec{ 255 Frequency: job.DailyKind, 256 AfterHour: 16, 257 BeforeHour: 24, 258 } 259 crontab = spec.ToRandomCrontab(seed) 260 fields = strings.Fields(crontab) 261 require.Len(t, fields, 6) 262 263 second, err = strconv.Atoi(fields[0]) 264 assert.NoError(t, err) 265 assert.True(t, 0 <= second && second < 60) 266 267 minute, err = strconv.Atoi(fields[1]) 268 assert.NoError(t, err) 269 assert.True(t, 0 <= minute && minute < 60) 270 271 hour, err = strconv.Atoi(fields[2]) 272 assert.NoError(t, err) 273 assert.True(t, 16 <= hour && hour < 24) 274 275 assert.Equal(t, fields[3], "*") // day of month 276 assert.Equal(t, fields[4], "*") // month 277 assert.Equal(t, fields[5], "*") // day of week 278 279 // Check that two successive executions are separated by about 7 days 280 schedule, err = cronParser.Parse(crontab) 281 require.NoError(t, err) 282 exec = schedule.Next(time.Now()) 283 next = schedule.Next(exec) 284 assert.WithinDuration(t, exec.Add(day), next, 3*time.Minute) 285 }