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) } }