go.charczuk.com@v0.0.0-20240327042549-bc490516bd1a/sdk/cron/parse_schedule_test.go (about) 1 /* 2 3 Copyright (c) 2023 - Present. Will Charczuk. All rights reserved. 4 Use of this source code is governed by a MIT license that can be found in the LICENSE file at the root of the repository. 5 6 */ 7 8 package cron 9 10 import ( 11 "errors" 12 "testing" 13 "time" 14 15 . "go.charczuk.com/sdk/assert" 16 ) 17 18 type stringScheduleTestCase struct { 19 Input string 20 ExpectedErr error 21 ExpectedNow bool 22 Expected time.Time 23 After time.Time 24 } 25 26 func TestParseSchedule(t *testing.T) { 27 testCases := []stringScheduleTestCase{ 28 {Input: "", ExpectedErr: ErrStringScheduleInvalid}, 29 {Input: newRandomID(), ExpectedErr: ErrStringScheduleInvalid}, 30 {Input: "*/1", ExpectedErr: ErrStringScheduleInvalid}, 31 {Input: "*/1 *", ExpectedErr: ErrStringScheduleInvalid}, 32 {Input: "*/1 * *", ExpectedErr: ErrStringScheduleInvalid}, 33 {Input: "*/1 * * *", ExpectedErr: ErrStringScheduleInvalid}, 34 {Input: "*/1 * * * * * *", After: time.Date(2018, 12, 29, 13, 12, 11, 10, time.UTC), Expected: time.Date(2018, 12, 29, 13, 12, 12, 0, time.UTC)}, 35 {Input: "*/5 * * * * * *", After: time.Date(2018, 12, 29, 13, 12, 11, 0, time.UTC), Expected: time.Date(2018, 12, 29, 13, 12, 15, 0, time.UTC)}, 36 {Input: "\t*/5 * * * * * *\t\n", After: time.Date(2018, 12, 29, 13, 12, 11, 0, time.UTC), Expected: time.Date(2018, 12, 29, 13, 12, 15, 0, time.UTC)}, 37 {Input: "* 2 1 * * 1-6 *", After: time.Date(2019, 01, 01, 12, 0, 0, 0, time.UTC), Expected: time.Date(2019, 01, 02, 01, 02, 0, 0, time.UTC)}, 38 {Input: "* 2 1 * * MON-FRI *", After: time.Date(2019, 01, 01, 12, 0, 0, 0, time.UTC), Expected: time.Date(2019, 01, 02, 01, 02, 0, 0, time.UTC)}, 39 {Input: "* 9 10 * * SUN-TUE *", After: time.Date(2019, 01, 02, 12, 0, 0, 0, time.UTC), Expected: time.Date(2019, 01, 06, 10, 9, 0, 0, time.UTC)}, 40 {Input: "0 0 0 * * 0 *", After: time.Date(2019, 01, 02, 12, 3, 4, 5, time.UTC), Expected: time.Date(2019, 01, 06, 0, 0, 0, 0, time.UTC)}, // every week at midnight sat/sun 41 {Input: "0 0 0 * * 0 *", After: time.Date(2019, 01, 06, 0, 0, 0, 1, time.UTC), Expected: time.Date(2019, 01, 13, 0, 0, 0, 0, time.UTC)}, // every week at midnight sat/sun (on almost exactly the same time) 42 {Input: "0 0 0 * * * *", After: time.Date(2019, 01, 02, 12, 3, 4, 5, time.UTC), Expected: time.Date(2019, 01, 03, 0, 0, 0, 0, time.UTC)}, // every day at midnight 43 {Input: "0 0 * * * * *", After: time.Date(2019, 01, 02, 12, 3, 4, 5, time.UTC), Expected: time.Date(2019, 01, 02, 13, 0, 0, 0, time.UTC)}, // every hour on the hour 44 {Input: "0 * * * *", After: time.Date(2019, 01, 02, 12, 3, 4, 5, time.UTC), Expected: time.Date(2019, 01, 02, 13, 0, 0, 0, time.UTC)}, // every hour on the hour (5 field) 45 {Input: "0 0 * * * *", After: time.Date(2019, 01, 02, 12, 3, 4, 5, time.UTC), Expected: time.Date(2019, 01, 02, 13, 0, 0, 0, time.UTC)}, // every hour on the hour (6 field) 46 {Input: "@never", After: time.Date(2019, 01, 02, 12, 3, 4, 5, time.UTC), Expected: time.Time{}}, // never shorthand 47 {Input: "@daily", After: time.Date(2019, 01, 02, 12, 3, 4, 5, time.UTC), Expected: time.Date(2019, 01, 03, 0, 0, 0, 0, time.UTC)}, // daily shorthand 48 {Input: "@hourly", After: time.Date(2019, 01, 02, 12, 3, 4, 5, time.UTC), Expected: time.Date(2019, 01, 02, 13, 0, 0, 0, time.UTC)}, // hourly shorthand 49 {Input: "@every not-a-value", ExpectedErr: ErrStringScheduleInvalid}, // every 50 {Input: "@every 500ms", After: time.Date(2019, 01, 02, 12, 3, 4, 0, time.UTC), Expected: time.Date(2019, 01, 02, 12, 3, 4, int(500*time.Millisecond), time.UTC)}, // every 51 {Input: "@once-at not-a-value", ExpectedErr: ErrStringScheduleInvalid}, // every 52 {Input: "@once-at 2019-01-02T13:14:15.555Z", After: time.Date(2019, 01, 02, 13, 14, 14, 0, time.UTC), Expected: time.Date(2019, 01, 02, 13, 14, 15, int(555*time.Millisecond), time.UTC)}, // every 53 {Input: "@immediately", After: time.Date(2019, 01, 02, 12, 3, 4, 0, time.UTC), ExpectedNow: true}, // immediately then every 54 {Input: "@immediately-then @every 500ms", After: time.Date(2019, 01, 02, 12, 3, 4, 0, time.UTC), ExpectedNow: true}, // immediately then every 55 } 56 57 for _, tc := range testCases { 58 parsed, err := ParseSchedule(tc.Input) 59 if tc.ExpectedErr != nil { 60 ItsNotNil(t, tc.Input) 61 ItsEqual(t, true, errors.Is(err, tc.ExpectedErr), tc.ExpectedErr.Error(), " vs. ", err.Error()) 62 } else if tc.ExpectedNow { 63 ItsNil(t, err) 64 next := parsed.Next(tc.After) 65 ItsEqual(t, true, inTimeDelta(Now(), next, time.Second), tc.Input) 66 } else { 67 ItsNil(t, err, tc.Input) 68 next := parsed.Next(tc.After) 69 ItsEqual(t, tc.Expected, next, tc.Input) 70 } 71 } 72 } 73 74 func Test_ParseSchedule_Every(t *testing.T) { 75 schedule, err := ParseSchedule("*/1 * * * * * *") 76 ItsNil(t, err) 77 last := time.Date(2019, 01, 29, 0, 0, 0, 0, time.UTC) 78 for x := 0; x < 60*60*6; x++ { 79 last = schedule.Next(last) 80 } 81 ItsEqual(t, time.Date(2019, 01, 29, 6, 0, 0, 0, time.UTC), last) 82 } 83 84 func Test_mapKeysToArray(t *testing.T) { 85 ItsEqual(t, []int{1, 2, 3}, mapKeysToArray(map[int]bool{ 86 3: true, 87 1: true, 88 2: true, 89 })) 90 ItsEqual(t, 0, len(mapKeysToArray(nil))) 91 ItsEqual(t, 0, len(mapKeysToArray(map[int]bool{}))) 92 } 93 94 func Test_ParseString_immediately(t *testing.T) { 95 var parsed Schedule 96 var err error 97 var input string 98 var now, next time.Time 99 after := time.Date(2018, 12, 29, 13, 12, 11, 10, time.UTC) 100 101 now = Now() 102 input = "@immediately-then */1 * * * * *" 103 parsed, err = ParseSchedule(input) 104 ItsNil(t, err) 105 106 next = parsed.Next(after) 107 108 ItsEqual(t, false, inTimeDelta(after, next, time.Second)) 109 ItsEqual(t, true, inTimeDelta(now, next, time.Second)) 110 next = parsed.Next(after) // should kick in real schedule 111 ItsEqual(t, true, inTimeDelta(time.Date(2018, 12, 29, 13, 12, 12, 10, time.UTC), next, time.Millisecond)) 112 113 input = "@immediately-then bogus" 114 parsed, err = ParseSchedule(input) 115 ItsEqual(t, true, errors.Is(err, ErrStringScheduleInvalid)) 116 ItsNil(t, parsed) 117 118 now = Now() 119 input = "@immediately-then @every 500ms" 120 parsed, err = ParseSchedule(input) 121 ItsNil(t, err) 122 next = parsed.Next(after) 123 ItsEqual(t, false, inTimeDelta(after, next, time.Second)) // should be now 124 ItsEqual(t, true, inTimeDelta(now, next, time.Second)) // should be now 125 126 next = parsed.Next(after) // should kick in real schedule 127 ItsEqual(t, true, inTimeDelta(time.Date(2018, 12, 29, 13, 12, 11, 10+int(500*time.Millisecond), time.UTC), next, time.Millisecond)) 128 }