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  }