231 lines
5.5 KiB
Go
231 lines
5.5 KiB
Go
package listdeliveries
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"testing"
|
|
"time"
|
|
|
|
"galaxy/mail/internal/domain/common"
|
|
deliverydomain "galaxy/mail/internal/domain/delivery"
|
|
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
func TestServiceExecuteAppliesDefaultLimit(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
store := &stubStore{
|
|
result: Result{
|
|
Items: []deliverydomain.Delivery{validDelivery("delivery-default", "notification:delivery-default")},
|
|
},
|
|
}
|
|
service := newTestService(t, Config{Store: store})
|
|
|
|
result, err := service.Execute(context.Background(), Input{})
|
|
require.NoError(t, err)
|
|
require.Len(t, result.Items, 1)
|
|
require.Equal(t, DefaultLimit, store.lastInput.Limit)
|
|
}
|
|
|
|
func TestInputValidateRejectsInvalidFiltersAndCursor(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
validCursor := Cursor{
|
|
CreatedAt: time.Unix(1_775_121_700, 0).UTC(),
|
|
DeliveryID: common.DeliveryID("delivery-cursor"),
|
|
}
|
|
validFrom := time.Unix(1_775_121_700, 0).UTC()
|
|
validTo := validFrom.Add(time.Minute)
|
|
|
|
tests := []struct {
|
|
name string
|
|
input Input
|
|
wantErr string
|
|
}{
|
|
{
|
|
name: "invalid recipient",
|
|
input: Input{
|
|
Filters: Filters{Recipient: common.Email("not-an-email")},
|
|
},
|
|
wantErr: "recipient:",
|
|
},
|
|
{
|
|
name: "invalid status",
|
|
input: Input{
|
|
Filters: Filters{Status: deliverydomain.Status("bad")},
|
|
},
|
|
wantErr: `status "bad" is unsupported`,
|
|
},
|
|
{
|
|
name: "invalid source",
|
|
input: Input{
|
|
Filters: Filters{Source: deliverydomain.Source("bad")},
|
|
},
|
|
wantErr: `source "bad" is unsupported`,
|
|
},
|
|
{
|
|
name: "invalid template id",
|
|
input: Input{
|
|
Filters: Filters{TemplateID: common.TemplateID(" bad-template")},
|
|
},
|
|
wantErr: "template id:",
|
|
},
|
|
{
|
|
name: "invalid idempotency key",
|
|
input: Input{
|
|
Filters: Filters{IdempotencyKey: common.IdempotencyKey(" bad-key")},
|
|
},
|
|
wantErr: "idempotency key:",
|
|
},
|
|
{
|
|
name: "invalid created at range",
|
|
input: Input{
|
|
Filters: Filters{
|
|
FromCreatedAt: &validTo,
|
|
ToCreatedAt: &validFrom,
|
|
},
|
|
},
|
|
wantErr: "from created at must not be after to created at",
|
|
},
|
|
{
|
|
name: "invalid cursor",
|
|
input: Input{
|
|
Cursor: &Cursor{
|
|
CreatedAt: time.Time{},
|
|
DeliveryID: common.DeliveryID("delivery-cursor"),
|
|
},
|
|
},
|
|
wantErr: "cursor:",
|
|
},
|
|
{
|
|
name: "valid cursor and filters",
|
|
input: Input{
|
|
Limit: 1,
|
|
Cursor: &Cursor{
|
|
CreatedAt: validCursor.CreatedAt,
|
|
DeliveryID: validCursor.DeliveryID,
|
|
},
|
|
Filters: Filters{
|
|
Recipient: common.Email("pilot@example.com"),
|
|
Status: deliverydomain.StatusSent,
|
|
Source: deliverydomain.SourceNotification,
|
|
TemplateID: common.TemplateID("auth.login_code"),
|
|
IdempotencyKey: common.IdempotencyKey("notification:delivery-123"),
|
|
FromCreatedAt: &validFrom,
|
|
ToCreatedAt: &validTo,
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
tt := tt
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
err := tt.input.Validate()
|
|
if tt.wantErr == "" {
|
|
require.NoError(t, err)
|
|
return
|
|
}
|
|
|
|
require.Error(t, err)
|
|
require.ErrorContains(t, err, tt.wantErr)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestServiceExecutePropagatesInvalidCursor(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
service := newTestService(t, Config{
|
|
Store: &stubStore{listErr: ErrInvalidCursor},
|
|
})
|
|
|
|
_, err := service.Execute(context.Background(), Input{Limit: 1})
|
|
require.ErrorIs(t, err, ErrInvalidCursor)
|
|
}
|
|
|
|
func TestServiceExecuteWrapsServiceUnavailable(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
service := newTestService(t, Config{
|
|
Store: &stubStore{listErr: errors.New("redis unavailable")},
|
|
})
|
|
|
|
_, err := service.Execute(context.Background(), Input{Limit: 1})
|
|
require.ErrorIs(t, err, ErrServiceUnavailable)
|
|
require.ErrorContains(t, err, "redis unavailable")
|
|
}
|
|
|
|
func TestServiceExecuteRejectsOversizedResult(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
service := newTestService(t, Config{
|
|
Store: &stubStore{
|
|
result: Result{
|
|
Items: []deliverydomain.Delivery{
|
|
validDelivery("delivery-one", "notification:delivery-one"),
|
|
validDelivery("delivery-two", "notification:delivery-two"),
|
|
},
|
|
},
|
|
},
|
|
})
|
|
|
|
_, err := service.Execute(context.Background(), Input{Limit: 1})
|
|
require.ErrorIs(t, err, ErrServiceUnavailable)
|
|
require.ErrorContains(t, err, "returned 2 items for limit 1")
|
|
}
|
|
|
|
type stubStore struct {
|
|
lastInput Input
|
|
result Result
|
|
listErr error
|
|
}
|
|
|
|
func (store *stubStore) List(_ context.Context, input Input) (Result, error) {
|
|
store.lastInput = input
|
|
if store.listErr != nil {
|
|
return Result{}, store.listErr
|
|
}
|
|
|
|
return store.result, nil
|
|
}
|
|
|
|
func newTestService(t *testing.T, cfg Config) *Service {
|
|
t.Helper()
|
|
|
|
service, err := New(cfg)
|
|
require.NoError(t, err)
|
|
|
|
return service
|
|
}
|
|
|
|
func validDelivery(deliveryID string, idempotencyKey common.IdempotencyKey) deliverydomain.Delivery {
|
|
createdAt := time.Unix(1_775_121_700, 0).UTC()
|
|
updatedAt := createdAt.Add(time.Minute)
|
|
sentAt := updatedAt.Add(time.Second)
|
|
|
|
record := deliverydomain.Delivery{
|
|
DeliveryID: common.DeliveryID(deliveryID),
|
|
Source: deliverydomain.SourceNotification,
|
|
PayloadMode: deliverydomain.PayloadModeRendered,
|
|
Envelope: deliverydomain.Envelope{To: []common.Email{common.Email("pilot@example.com")}},
|
|
Content: deliverydomain.Content{Subject: "Ready", TextBody: "Turn ready"},
|
|
IdempotencyKey: idempotencyKey,
|
|
Status: deliverydomain.StatusSent,
|
|
AttemptCount: 1,
|
|
CreatedAt: createdAt,
|
|
UpdatedAt: updatedAt,
|
|
SentAt: &sentAt,
|
|
}
|
|
if err := record.Validate(); err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
return record
|
|
}
|
|
|
|
var _ Store = (*stubStore)(nil)
|