package cronutil_test import ( "testing" "time" "galaxy/cronutil" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) func TestParseAcceptsFiveFieldExpressions(t *testing.T) { t.Parallel() accepted := []string{ "0 18 * * *", "*/15 * * * *", "0 */6 * * *", "0 */4 * * *", "0 0 * * *", "0 0 1 1 *", "30 9 * * MON-FRI", } for _, expr := range accepted { t.Run(expr, func(t *testing.T) { t.Parallel() _, err := cronutil.Parse(expr) require.NoError(t, err) }) } } func TestParseRejectsInvalidExpressions(t *testing.T) { t.Parallel() cases := []struct { name string expr string }{ {name: "empty", expr: ""}, {name: "whitespace only", expr: " "}, {name: "garbage", expr: "not a cron"}, {name: "six field with seconds", expr: "*/30 * * * * *"}, {name: "too few fields", expr: "0 18 *"}, {name: "minute out of range", expr: "60 * * * *"}, {name: "hour out of range", expr: "* 24 * * *"}, {name: "month out of range", expr: "* * * 13 *"}, } for _, tc := range cases { t.Run(tc.name, func(t *testing.T) { t.Parallel() _, err := cronutil.Parse(tc.expr) require.Error(t, err) }) } } func TestScheduleNextComputesExpectedFire(t *testing.T) { t.Parallel() schedule, err := cronutil.Parse("0 18 * * *") require.NoError(t, err) after := time.Date(2026, 1, 1, 0, 0, 0, 0, time.UTC) next := schedule.Next(after) assert.Equal(t, time.Date(2026, 1, 1, 18, 0, 0, 0, time.UTC), next) assert.Equal(t, time.UTC, next.Location()) } func TestScheduleNextStepsToNextFifteenMinuteSlot(t *testing.T) { t.Parallel() schedule, err := cronutil.Parse("*/15 * * * *") require.NoError(t, err) after := time.Date(2026, 6, 30, 12, 7, 33, 0, time.UTC) next := schedule.Next(after) assert.Equal(t, time.Date(2026, 6, 30, 12, 15, 0, 0, time.UTC), next) assert.Equal(t, time.UTC, next.Location()) } func TestScheduleNextReturnsUTCForNonUTCInput(t *testing.T) { t.Parallel() schedule, err := cronutil.Parse("0 18 * * *") require.NoError(t, err) moscow, err := time.LoadLocation("Europe/Moscow") require.NoError(t, err) after := time.Date(2026, 1, 1, 12, 0, 0, 0, moscow) // 09:00 UTC next := schedule.Next(after) assert.Equal(t, time.Date(2026, 1, 1, 18, 0, 0, 0, time.UTC), next) assert.Equal(t, time.UTC, next.Location()) }