166 lines
4.4 KiB
Go
166 lines
4.4 KiB
Go
package automations
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"testing"
|
|
"time"
|
|
|
|
"git.yeij.top/AskaEth/Cyrene_v2/internal/bus"
|
|
)
|
|
|
|
func TestValidateCron(t *testing.T) {
|
|
t.Parallel()
|
|
s, err := New(Config{}, Deps{Bus: bus.New()})
|
|
if err != nil {
|
|
t.Fatalf("New: %v", err)
|
|
}
|
|
tests := []struct {
|
|
name string
|
|
expr string
|
|
cfg Config
|
|
want bool
|
|
}{
|
|
{name: "standard 5-field", expr: "*/5 * * * *", want: true},
|
|
{name: "standard 5-field with names", expr: "0 9 * * MON", want: true},
|
|
{name: "descriptor every", expr: "@every 1h30m", want: true},
|
|
{name: "descriptor midnight", expr: "@midnight", want: true},
|
|
{name: "6-field rejected by default", expr: "* * * * * *", want: false},
|
|
{name: "6-field accepted with seconds", expr: "*/10 * * * * *", cfg: Config{WithSeconds: true}, want: true},
|
|
{name: "garbage rejected", expr: "not a cron", want: false},
|
|
{name: "empty rejected", expr: "", want: false},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
t.Parallel()
|
|
ss := s
|
|
if tt.cfg.WithSeconds {
|
|
ss, err = New(tt.cfg, Deps{Bus: bus.New()})
|
|
if err != nil {
|
|
t.Fatalf("New: %v", err)
|
|
}
|
|
}
|
|
err := ss.ValidateCron(tt.expr)
|
|
if got := err == nil; got != tt.want {
|
|
t.Fatalf("ValidateCron(%q) err = %v, wantValid=%v", tt.expr, err, tt.want)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestAddSchedule_InvalidCronRejected(t *testing.T) {
|
|
t.Parallel()
|
|
s, err := New(Config{}, Deps{Bus: bus.New()})
|
|
if err != nil {
|
|
t.Fatalf("New: %v", err)
|
|
}
|
|
act, err := NewFuncAction("x", func(context.Context, any) (any, error) { return nil, nil })
|
|
if err != nil {
|
|
t.Fatalf("NewFuncAction: %v", err)
|
|
}
|
|
if _, err := s.AddSchedule(context.Background(), "not a cron", act); !errors.Is(err, ErrInvalidCron) {
|
|
t.Fatalf("err = %v, want ErrInvalidCron", err)
|
|
}
|
|
if _, err := s.AddSchedule(context.Background(), "* * * * * *", act); !errors.Is(err, ErrInvalidCron) {
|
|
t.Fatalf("6-field without seconds: err = %v, want ErrInvalidCron", err)
|
|
}
|
|
}
|
|
|
|
func TestScheduler_RejectsNilAction(t *testing.T) {
|
|
t.Parallel()
|
|
s, err := New(Config{}, Deps{Bus: bus.New()})
|
|
if err != nil {
|
|
t.Fatalf("New: %v", err)
|
|
}
|
|
if _, err := s.AddSchedule(context.Background(), "*/5 * * * *", nil); !errors.Is(err, ErrNilAction) {
|
|
t.Fatalf("err = %v, want ErrNilAction", err)
|
|
}
|
|
}
|
|
|
|
func TestScheduler_StartStopIdempotent(t *testing.T) {
|
|
t.Parallel()
|
|
s, err := New(Config{}, Deps{Bus: bus.New()})
|
|
if err != nil {
|
|
t.Fatalf("New: %v", err)
|
|
}
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
defer cancel()
|
|
|
|
if err := s.Start(ctx); err != nil {
|
|
t.Fatalf("Start: %v", err)
|
|
}
|
|
if err := s.Start(ctx); err != nil {
|
|
t.Fatalf("second Start: %v", err)
|
|
}
|
|
if err := s.Stop(ctx); err != nil {
|
|
t.Fatalf("Stop: %v", err)
|
|
}
|
|
if err := s.Stop(ctx); err != nil {
|
|
t.Fatalf("second Stop: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestScheduler_FiresOnDue(t *testing.T) {
|
|
t.Parallel()
|
|
b := bus.New()
|
|
defer b.Close()
|
|
|
|
got := make(chan bus.Event, 1)
|
|
unsub, err := b.Subscribe(context.Background(), SubjectResult, func(_ context.Context, ev bus.Event) error {
|
|
got <- ev
|
|
return nil
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("subscribe: %v", err)
|
|
}
|
|
defer unsub()
|
|
|
|
s, err := New(Config{}, Deps{Bus: b})
|
|
if err != nil {
|
|
t.Fatalf("New: %v", err)
|
|
}
|
|
act, err := NewFuncAction("tick", func(context.Context, any) (any, error) { return "ok", nil })
|
|
if err != nil {
|
|
t.Fatalf("NewFuncAction: %v", err)
|
|
}
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
defer cancel()
|
|
if _, err := s.AddSchedule(ctx, "@every 50ms", act); err != nil {
|
|
t.Fatalf("AddSchedule: %v", err)
|
|
}
|
|
if err := s.Start(ctx); err != nil {
|
|
t.Fatalf("Start: %v", err)
|
|
}
|
|
|
|
select {
|
|
case ev := <-got:
|
|
res, ok := ev.Payload.(Result)
|
|
if !ok {
|
|
t.Fatalf("payload type = %T, want Result", ev.Payload)
|
|
}
|
|
if res.Kind != "schedule" || res.Action != "tick" || res.Output != "ok" {
|
|
t.Fatalf("result = %+v, want {schedule tick ok}", res)
|
|
}
|
|
case <-time.After(3 * time.Second):
|
|
t.Fatal("schedule did not fire")
|
|
}
|
|
if err := s.Stop(ctx); err != nil {
|
|
t.Fatalf("Stop: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestScheduler_WithSecondsAcceptsSixField(t *testing.T) {
|
|
t.Parallel()
|
|
s, err := New(Config{WithSeconds: true}, Deps{Bus: bus.New()})
|
|
if err != nil {
|
|
t.Fatalf("New: %v", err)
|
|
}
|
|
act, err := NewFuncAction("tick", func(context.Context, any) (any, error) { return nil, nil })
|
|
if err != nil {
|
|
t.Fatalf("NewFuncAction: %v", err)
|
|
}
|
|
if _, err := s.AddSchedule(context.Background(), "*/1 * * * * *", act); err != nil {
|
|
t.Fatalf("6-field with seconds rejected: %v", err)
|
|
}
|
|
}
|