package notification import ( "context" "errors" "testing" "time" ) type dispatcherStore struct { items []Outbox complete []bool } func (store *dispatcherStore) Enqueue(context.Context, Outbox) (Outbox, bool, error) { return Outbox{}, false, nil } func (store *dispatcherStore) GetOutbox(context.Context, string) (Outbox, error) { return Outbox{}, ErrNotFound } func (store *dispatcherStore) ClaimDue(context.Context, time.Time, int) ([]Outbox, error) { items := store.items store.items = nil return items, nil } func (store *dispatcherStore) Complete(_ context.Context, _ string, _ int, success bool, _ error, _ time.Time) (Outbox, error) { store.complete = append(store.complete, success) return Outbox{Status: StatusDelivered}, nil } func TestDispatcherDispatchesRecoveryAndRecordsFailure(t *testing.T) { store := &dispatcherStore{items: []Outbox{{ID: "delivery-1", IdempotencyKey: "key-1", ChannelID: "memory", EventType: EventRecovery, Subject: "Recovered", Body: "recovered", Attempts: 1}}} channel := &MemoryChannel{} dispatcher := Dispatcher{Store: store, Channels: map[string]ChannelSender{"memory": channel}} delivered, err := dispatcher.DispatchDue(context.Background(), time.Unix(100, 0), 1) if err != nil || delivered != 1 || len(channel.Deliveries) != 1 || channel.Deliveries[0].EventType != EventRecovery { t.Fatalf("delivered=%d channel=%+v err=%v", delivered, channel.Deliveries, err) } if len(store.complete) != 1 || !store.complete[0] { t.Fatalf("completion=%v", store.complete) } store.items = []Outbox{{ID: "delivery-2", IdempotencyKey: "key-2", ChannelID: "memory", EventType: EventFiring, Subject: "Firing", Body: "firing", Attempts: 1}} channel.Failure = errors.New("channel unavailable") delivered, err = dispatcher.DispatchDue(context.Background(), time.Unix(101, 0), 1) if err != nil || delivered != 0 || len(store.complete) != 2 || store.complete[1] { t.Fatalf("failed dispatch delivered=%d completion=%v err=%v", delivered, store.complete, err) } } func TestDispatcherMissingSenderFailsSafely(t *testing.T) { store := &dispatcherStore{items: []Outbox{{ID: "delivery-3", IdempotencyKey: "key-3", ChannelID: "missing", EventType: EventUnknown, Subject: "Unknown", Body: "unknown", Attempts: 1}}} dispatcher := Dispatcher{Store: store, Channels: map[string]ChannelSender{}} if _, err := dispatcher.DispatchDue(context.Background(), time.Unix(100, 0), 1); err != nil { t.Fatal(err) } if len(store.complete) != 1 || store.complete[0] { t.Fatalf("missing sender completion=%v", store.complete) } }