mirror of https://github.com/usememos/memos.git
128 lines
3.5 KiB
Go
128 lines
3.5 KiB
Go
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)
|
|
}
|
|
}
|