package automations import ( "context" "errors" "testing" "time" "git.yeij.top/AskaEth/Cyrene_v2/internal/bus" ) func TestReminder_AddListCancel(t *testing.T) { t.Parallel() rs, err := NewReminder(Config{}, Deps{Bus: bus.New()}) if err != nil { t.Fatalf("NewReminder: %v", err) } base := time.Now().Add(time.Hour) r1 := Reminder{ID: "later", At: base.Add(time.Hour), Message: "b"} r2 := Reminder{ID: "sooner", At: base, Message: "a"} if err := rs.Add(context.Background(), r1); err != nil { t.Fatalf("Add r1: %v", err) } if err := rs.Add(context.Background(), r2); err != nil { t.Fatalf("Add r2: %v", err) } if err := rs.Add(context.Background(), r2); !errors.Is(err, ErrDuplicate) { t.Fatalf("duplicate: err = %v, want ErrDuplicate", err) } list := rs.List() if len(list) != 2 { t.Fatalf("len(List) = %d, want 2", len(list)) } // Sorted by At ascending. if list[0].ID != "sooner" || list[1].ID != "later" { t.Fatalf("not sorted by At: %+v", list) } if err := rs.Cancel(context.Background(), "sooner"); err != nil { t.Fatalf("Cancel: %v", err) } if got := len(rs.List()); got != 1 { t.Fatalf("len(List) after cancel = %d, want 1", got) } if err := rs.Cancel(context.Background(), "sooner"); !errors.Is(err, ErrNotFound) { t.Fatalf("cancel missing: err = %v, want ErrNotFound", err) } if err := rs.Add(context.Background(), Reminder{ID: "", At: base}); !errors.Is(err, ErrInvalidRule) { t.Fatalf("empty id: err = %v, want ErrInvalidRule", err) } if err := rs.Add(context.Background(), Reminder{ID: "x"}); !errors.Is(err, ErrInvalidRule) { t.Fatalf("zero At: err = %v, want ErrInvalidRule", err) } } func TestReminder_FiresOnDue(t *testing.T) { t.Parallel() b := bus.New() defer b.Close() got := make(chan bus.Event, 1) unsub, err := b.Subscribe(context.Background(), SubjectTrigger, func(_ context.Context, ev bus.Event) error { got <- ev return nil }) if err != nil { t.Fatalf("subscribe: %v", err) } defer unsub() rs, err := NewReminder(Config{ReminderInterval: 10 * time.Millisecond}, Deps{Bus: b}) if err != nil { t.Fatalf("NewReminder: %v", err) } ctx, cancel := context.WithCancel(context.Background()) defer cancel() r := Reminder{ID: "wake", At: time.Now().Add(20 * time.Millisecond), Message: "time to focus"} if err := rs.Add(ctx, r); err != nil { t.Fatalf("Add: %v", err) } if err := rs.Start(ctx); err != nil { t.Fatalf("Start: %v", err) } select { case ev := <-got: rem, ok := ev.Payload.(Reminder) if !ok { t.Fatalf("payload type = %T, want Reminder", ev.Payload) } if rem.ID != "wake" || rem.Message != "time to focus" { t.Fatalf("reminder = %+v", rem) } case <-time.After(3 * time.Second): t.Fatal("reminder did not fire") } if err := rs.Stop(ctx); err != nil { t.Fatalf("Stop: %v", err) } } func TestReminder_StartStopIdempotent(t *testing.T) { t.Parallel() rs, err := NewReminder(Config{}, Deps{Bus: bus.New()}) if err != nil { t.Fatalf("NewReminder: %v", err) } ctx, cancel := context.WithCancel(context.Background()) defer cancel() if err := rs.Start(ctx); err != nil { t.Fatalf("Start: %v", err) } if err := rs.Start(ctx); err != nil { t.Fatalf("second Start: %v", err) } if err := rs.Stop(ctx); err != nil { t.Fatalf("Stop: %v", err) } if err := rs.Stop(ctx); err != nil { t.Fatalf("second Stop: %v", err) } } func TestReminder_ConcurrentAddCancelList(t *testing.T) { t.Parallel() rs, err := NewReminder(Config{}, Deps{Bus: bus.New()}) if err != nil { t.Fatalf("NewReminder: %v", err) } const n = 50 done := make(chan struct{}) for i := 0; i < n; i++ { go func(i int) { defer func() { done <- struct{}{} }() id := "r" _ = rs.Add(context.Background(), Reminder{ID: id, At: time.Now().Add(time.Hour), Message: "m"}) _ = rs.List() _ = rs.Cancel(context.Background(), id) }(i) } for i := 0; i < n; i++ { <-done } } func TestService_ReminderLifecycle(t *testing.T) { t.Parallel() b := bus.New() defer b.Close() s, err := NewService(Config{}, Deps{Bus: b}) if err != nil { t.Fatalf("NewService: %v", err) } ctx, cancel := context.WithCancel(context.Background()) defer cancel() if err := s.AddReminder(ctx, Reminder{ID: "x", At: time.Now().Add(time.Hour), Message: "m"}); err != nil { t.Fatalf("AddReminder: %v", err) } if got := len(s.Reminders()); got != 1 { t.Fatalf("len(Reminders) = %d, want 1", got) } if err := s.CancelReminder(ctx, "x"); err != nil { t.Fatalf("CancelReminder: %v", err) } }