T2.2: Migrate scheduler to use domain.Schedule

Parse each job's schedule once on load (resetNextRuns) and on edit
(RefreshSchedule) via the new parseJobSchedule helper, caching the
result in a map[int]domain.Schedule keyed by job ID. prepareNextRun
now looks up the cached Schedule instead of re-parsing the string on
every call. Remove the nextRunTime wrapper that did the per-call
parsing. Drop the three scheduler_test.go tests that duplicated
coverage already in domain/schedule_test.go.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
mixeme
2026-06-18 22:59:46 +03:00
parent 0f17782174
commit ca673f08f9
3 changed files with 33 additions and 71 deletions
+5 -48
View File
@@ -8,32 +8,10 @@ import (
"gitea.mixdep.ru/mix/gosentry/src/domain"
)
func TestNextRunTimeRejectsInvalidSchedules(t *testing.T) {
from := time.Date(2026, 6, 14, 12, 0, 0, 0, time.UTC)
cases := []struct {
schedule string
desc string
}{
{"", "empty string"},
{" ", "whitespace only"},
{"@every", "bare @every without duration"},
{"@every xyz", "invalid @every duration string"},
{"@every -1s", "negative @every duration"},
{"@every 0s", "zero @every duration"},
{"not-a-cron", "invalid cron expression"},
{"60 * * * *", "cron minute out of range"},
}
for _, tc := range cases {
_, ok := nextRunTime(tc.schedule, from)
if ok {
t.Errorf("nextRunTime(%q) [%s]: expected false, got true", tc.schedule, tc.desc)
}
}
}
func TestPrepareNextRunSetsDisplayString(t *testing.T) {
jobs := []domain.Job{{Schedule: "*/5 * * * *", Enabled: true}}
s := &Scheduler{jobs: &jobs}
s := &Scheduler{jobs: &jobs, schedules: make(map[int]domain.Schedule)}
s.parseJobSchedule(&jobs[0])
from := time.Date(2026, 6, 14, 12, 3, 0, 0, time.UTC)
s.prepareNextRun(&jobs[0], from)
@@ -50,7 +28,9 @@ func TestPrepareNextRunSetsDisplayString(t *testing.T) {
func TestPrepareNextRunSetsInvalidScheduleLabel(t *testing.T) {
jobs := []domain.Job{{Schedule: "not-a-cron", Enabled: true}}
s := &Scheduler{jobs: &jobs}
s := &Scheduler{jobs: &jobs, schedules: make(map[int]domain.Schedule)}
// parseJobSchedule will drop the invalid spec, so schedules map stays empty.
s.parseJobSchedule(&jobs[0])
s.prepareNextRun(&jobs[0], time.Now())
@@ -62,29 +42,6 @@ func TestPrepareNextRunSetsInvalidScheduleLabel(t *testing.T) {
}
}
func TestNextRunTimeSupportsEvery(t *testing.T) {
from := time.Date(2026, 6, 14, 12, 0, 0, 0, time.UTC)
next, ok := nextRunTime("@every 10s", from)
if !ok {
t.Fatal("expected @every schedule to parse")
}
if want := from.Add(10 * time.Second); !next.Equal(want) {
t.Fatalf("expected %s, got %s", want, next)
}
}
func TestNextRunTimeSupportsCron(t *testing.T) {
from := time.Date(2026, 6, 14, 12, 3, 0, 0, time.UTC)
next, ok := nextRunTime("*/5 * * * *", from)
if !ok {
t.Fatal("expected cron schedule to parse")
}
want := time.Date(2026, 6, 14, 12, 5, 0, 0, time.UTC)
if !next.Equal(want) {
t.Fatalf("expected %s, got %s", want, next)
}
}
func TestRunningOutputIncludesInvocation(t *testing.T) {
started := time.Date(2026, 6, 17, 23, 40, 0, 0, time.Local)
job := domain.Job{