-
-
Notifications
You must be signed in to change notification settings - Fork 4.7k
Expand file tree
/
Copy pathparser_test.go
More file actions
165 lines (149 loc) · 4.86 KB
/
Copy pathparser_test.go
File metadata and controls
165 lines (149 loc) · 4.86 KB
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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
package scheduler
import (
"testing"
"time"
)
func TestParseCronExpression(t *testing.T) {
tests := []struct {
name string
expr string
wantErr bool
}{
// Standard 5-field format
{"every minute", "* * * * *", false},
{"hourly", "0 * * * *", false},
{"daily midnight", "0 0 * * *", false},
{"weekly sunday", "0 0 * * 0", false},
{"monthly", "0 0 1 * *", false},
{"specific time", "30 14 * * *", false}, // 2:30 PM daily
{"range", "0 9-17 * * *", false}, // Every hour 9 AM - 5 PM
{"step", "*/15 * * * *", false}, // Every 15 minutes
{"list", "0 8,12,18 * * *", false}, // 8 AM, 12 PM, 6 PM
// 6-field format with seconds
{"with seconds", "0 * * * * *", false},
{"every 30 seconds", "*/30 * * * * *", false},
// Invalid expressions
{"empty", "", true},
{"too few fields", "* * *", true},
{"too many fields", "* * * * * * *", true},
{"invalid minute", "60 * * * *", true},
{"invalid hour", "0 24 * * *", true},
{"invalid day", "0 0 32 * *", true},
{"invalid month", "0 0 1 13 *", true},
{"invalid weekday", "0 0 * * 8", true},
{"garbage", "not a cron expression", true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
schedule, err := ParseCronExpression(tt.expr)
if (err != nil) != tt.wantErr {
t.Errorf("ParseCronExpression(%q) error = %v, wantErr %v", tt.expr, err, tt.wantErr)
return
}
if !tt.wantErr && schedule == nil {
t.Errorf("ParseCronExpression(%q) returned nil schedule without error", tt.expr)
}
})
}
}
func TestScheduleNext(t *testing.T) {
tests := []struct {
name string
expr string
from time.Time
expected time.Time
}{
{
name: "every minute from start of hour",
expr: "* * * * *",
from: time.Date(2025, 1, 1, 10, 0, 0, 0, time.UTC),
expected: time.Date(2025, 1, 1, 10, 1, 0, 0, time.UTC),
},
{
name: "hourly at minute 30",
expr: "30 * * * *",
from: time.Date(2025, 1, 1, 10, 0, 0, 0, time.UTC),
expected: time.Date(2025, 1, 1, 10, 30, 0, 0, time.UTC),
},
{
name: "hourly at minute 30 (already past)",
expr: "30 * * * *",
from: time.Date(2025, 1, 1, 10, 45, 0, 0, time.UTC),
expected: time.Date(2025, 1, 1, 11, 30, 0, 0, time.UTC),
},
{
name: "daily at 2 AM",
expr: "0 2 * * *",
from: time.Date(2025, 1, 1, 10, 0, 0, 0, time.UTC),
expected: time.Date(2025, 1, 2, 2, 0, 0, 0, time.UTC),
},
{
name: "every 15 minutes",
expr: "*/15 * * * *",
from: time.Date(2025, 1, 1, 10, 7, 0, 0, time.UTC),
expected: time.Date(2025, 1, 1, 10, 15, 0, 0, time.UTC),
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
schedule, err := ParseCronExpression(tt.expr)
if err != nil {
t.Fatalf("failed to parse expression: %v", err)
}
next := schedule.Next(tt.from)
if !next.Equal(tt.expected) {
t.Errorf("Next(%v) = %v, expected %v", tt.from, next, tt.expected)
}
})
}
}
func TestScheduleNextWithTimezone(t *testing.T) {
nyc, _ := time.LoadLocation("America/New_York")
// Schedule for 9 AM in New York
schedule, err := ParseCronExpression("0 9 * * *")
if err != nil {
t.Fatalf("failed to parse expression: %v", err)
}
// Current time: 8 AM in New York
from := time.Date(2025, 1, 1, 8, 0, 0, 0, nyc)
next := schedule.Next(from)
// Should be 9 AM same day in New York
expected := time.Date(2025, 1, 1, 9, 0, 0, 0, nyc)
if !next.Equal(expected) {
t.Errorf("Next(%v) = %v, expected %v", from, next, expected)
}
}
func TestScheduleWeekdaySevenIsSunday(t *testing.T) {
// Cron treats both 0 and 7 as Sunday, but time.Weekday never returns 7,
// so a schedule written with 7 must still fire on Sundays.
//
// Combine day-of-month 1 with weekday 7 so the day-of-month clause does not
// mask the weekday behavior for the (non-first-of-month) days exercised here.
// 2025-01-05 is a Sunday and 2025-01-06 is a Monday.
sunday := time.Date(2025, 1, 5, 0, 0, 0, 0, time.UTC)
monday := time.Date(2025, 1, 6, 0, 0, 0, 0, time.UTC)
seven, err := ParseCronExpression("0 0 1 * 7")
if err != nil {
t.Fatalf("failed to parse expression: %v", err)
}
if !seven.matches(sunday) {
t.Errorf("weekday 7 should match Sunday %v", sunday)
}
if seven.matches(monday) {
t.Errorf("weekday 7 should not match Monday %v", monday)
}
// Regression: weekday 0 (the canonical Sunday) keeps working.
zero, err := ParseCronExpression("0 0 1 * 0")
if err != nil {
t.Fatalf("failed to parse expression: %v", err)
}
if !zero.matches(sunday) {
t.Errorf("weekday 0 should match Sunday %v", sunday)
}
// End-to-end via the public Next: from a Thursday, the next fire of
// "0 0 1 * 7" is the upcoming Sunday (Jan 5), earlier than the next 1st.
from := time.Date(2025, 1, 2, 10, 0, 0, 0, time.UTC)
if next := seven.Next(from); !next.Equal(sunday) {
t.Errorf("Next(%v) = %v, expected Sunday %v", from, next, sunday)
}
}