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  }