memos/plugin/scheduler/parser_test.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)
}
}