mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-10-12 17:05:01 +03:00
b124af2a1b
The panel scheduler runs WithSeconds(), whose parser rejects a standard five-field crontab such as "30 8 * * *". An admin who typed one into the Telegram/Discord notify time or the LDAP sync schedule saved it without error, AddJob then failed at startup, and the job silently never ran. Admin-typed schedules now go through internal/util/cronspec, which takes an optional seconds column plus the @descriptors and hands the scheduler a parsed cron.Schedule. CheckValid rejects an unparseable schedule on save, and a stored legacy value falls back to the job's default instead of dropping the job.
37 lines
1.2 KiB
Go
37 lines
1.2 KiB
Go
package web
|
|
|
|
import (
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/robfig/cron/v3"
|
|
)
|
|
|
|
// A five-field crontab used to be refused by the WithSeconds() scheduler, so the
|
|
// Telegram/Discord report and LDAP sync were never scheduled at all.
|
|
func TestScheduleSettingJob(t *testing.T) {
|
|
from := time.Date(2026, 10, 11, 7, 0, 0, 0, time.UTC)
|
|
cases := []struct {
|
|
name, spec string
|
|
want time.Time
|
|
}{
|
|
{"five-field crontab", "30 8 * * *", time.Date(2026, 10, 11, 8, 30, 0, 0, time.UTC)},
|
|
{"six-field crontab", "15 30 8 * * *", time.Date(2026, 10, 11, 8, 30, 15, 0, time.UTC)},
|
|
{"empty falls back", " ", time.Date(2026, 10, 12, 0, 0, 0, 0, time.UTC)},
|
|
{"invalid falls back", "every morning", time.Date(2026, 10, 12, 0, 0, 0, 0, time.UTC)},
|
|
}
|
|
for _, c := range cases {
|
|
t.Run(c.name, func(t *testing.T) {
|
|
sched := cron.New(cron.WithSeconds(), cron.WithLocation(time.UTC))
|
|
id := scheduleSettingJob(sched, "test job", c.spec, "@daily", cron.FuncJob(func() {}))
|
|
entry := sched.Entry(id)
|
|
if entry.Schedule == nil {
|
|
t.Fatalf("spec %q: job was not scheduled", c.spec)
|
|
}
|
|
if got := entry.Schedule.Next(from); !got.Equal(c.want) {
|
|
t.Errorf("spec %q: next run %v, want %v", c.spec, got, c.want)
|
|
}
|
|
})
|
|
}
|
|
}
|