337 lines
8.4 KiB
Go
337 lines
8.4 KiB
Go
package store
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/tech/sendico/ledger/storage/model"
|
|
"github.com/tech/sendico/pkg/db/repository"
|
|
"github.com/tech/sendico/pkg/db/repository/builder"
|
|
rd "github.com/tech/sendico/pkg/db/repository/decoder"
|
|
"github.com/tech/sendico/pkg/db/storable"
|
|
"github.com/tech/sendico/pkg/merrors"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
"go.mongodb.org/mongo-driver/bson/primitive"
|
|
"go.mongodb.org/mongo-driver/mongo"
|
|
"go.uber.org/zap"
|
|
)
|
|
|
|
func TestOutboxStore_Create(t *testing.T) {
|
|
ctx := context.Background()
|
|
logger := zap.NewNop()
|
|
|
|
t.Run("Success", func(t *testing.T) {
|
|
var insertedEvent *model.OutboxEvent
|
|
stub := &repositoryStub{
|
|
InsertFunc: func(ctx context.Context, object storable.Storable, _ builder.Query) error {
|
|
insertedEvent = object.(*model.OutboxEvent)
|
|
return nil
|
|
},
|
|
}
|
|
|
|
store := &outboxStore{logger: logger, repo: stub}
|
|
event := &model.OutboxEvent{
|
|
EventID: "evt_12345",
|
|
Subject: "ledger.entry.created",
|
|
Payload: []byte(`{"entryId":"123"}`),
|
|
Status: model.OutboxStatusPending,
|
|
}
|
|
|
|
err := store.Create(ctx, event)
|
|
|
|
require.NoError(t, err)
|
|
assert.NotNil(t, insertedEvent)
|
|
assert.Equal(t, "evt_12345", insertedEvent.EventID)
|
|
assert.Equal(t, "ledger.entry.created", insertedEvent.Subject)
|
|
assert.Equal(t, model.OutboxStatusPending, insertedEvent.Status)
|
|
})
|
|
|
|
t.Run("NilEvent", func(t *testing.T) {
|
|
stub := &repositoryStub{}
|
|
store := &outboxStore{logger: logger, repo: stub}
|
|
|
|
err := store.Create(ctx, nil)
|
|
|
|
require.Error(t, err)
|
|
assert.True(t, errors.Is(err, merrors.ErrInvalidArg))
|
|
})
|
|
|
|
t.Run("DuplicateEventID", func(t *testing.T) {
|
|
stub := &repositoryStub{
|
|
InsertFunc: func(ctx context.Context, object storable.Storable, _ builder.Query) error {
|
|
return mongo.WriteException{
|
|
WriteErrors: []mongo.WriteError{
|
|
{Code: 11000}, // Duplicate key error
|
|
},
|
|
}
|
|
},
|
|
}
|
|
|
|
store := &outboxStore{logger: logger, repo: stub}
|
|
event := &model.OutboxEvent{
|
|
EventID: "duplicate_event",
|
|
Subject: "test.subject",
|
|
Status: model.OutboxStatusPending,
|
|
}
|
|
|
|
err := store.Create(ctx, event)
|
|
|
|
require.Error(t, err)
|
|
assert.True(t, errors.Is(err, merrors.ErrDataConflict))
|
|
})
|
|
|
|
t.Run("InsertError", func(t *testing.T) {
|
|
expectedErr := errors.New("database error")
|
|
stub := &repositoryStub{
|
|
InsertFunc: func(ctx context.Context, object storable.Storable, _ builder.Query) error {
|
|
return expectedErr
|
|
},
|
|
}
|
|
|
|
store := &outboxStore{logger: logger, repo: stub}
|
|
event := &model.OutboxEvent{
|
|
EventID: "evt_123",
|
|
Subject: "test.subject",
|
|
}
|
|
|
|
err := store.Create(ctx, event)
|
|
|
|
require.Error(t, err)
|
|
assert.Equal(t, expectedErr, err)
|
|
})
|
|
}
|
|
|
|
func TestOutboxStore_ListPending(t *testing.T) {
|
|
ctx := context.Background()
|
|
logger := zap.NewNop()
|
|
|
|
t.Run("Success", func(t *testing.T) {
|
|
called := false
|
|
stub := &repositoryStub{
|
|
FindManyByFilterFunc: func(ctx context.Context, _ builder.Query, decoder rd.DecodingFunc) error {
|
|
called = true
|
|
return nil
|
|
},
|
|
}
|
|
|
|
store := &outboxStore{logger: logger, repo: stub}
|
|
results, err := store.ListPending(ctx, 10)
|
|
|
|
require.NoError(t, err)
|
|
assert.True(t, called)
|
|
assert.NotNil(t, results)
|
|
})
|
|
|
|
t.Run("EmptyResult", func(t *testing.T) {
|
|
stub := &repositoryStub{
|
|
FindManyByFilterFunc: func(ctx context.Context, _ builder.Query, decoder rd.DecodingFunc) error {
|
|
return nil
|
|
},
|
|
}
|
|
|
|
store := &outboxStore{logger: logger, repo: stub}
|
|
results, err := store.ListPending(ctx, 10)
|
|
|
|
require.NoError(t, err)
|
|
assert.Len(t, results, 0)
|
|
})
|
|
|
|
t.Run("WithLimit", func(t *testing.T) {
|
|
called := false
|
|
stub := &repositoryStub{
|
|
FindManyByFilterFunc: func(ctx context.Context, _ builder.Query, decoder rd.DecodingFunc) error {
|
|
called = true
|
|
return nil
|
|
},
|
|
}
|
|
|
|
store := &outboxStore{logger: logger, repo: stub}
|
|
results, err := store.ListPending(ctx, 3)
|
|
|
|
require.NoError(t, err)
|
|
assert.True(t, called)
|
|
assert.NotNil(t, results)
|
|
})
|
|
|
|
t.Run("FindError", func(t *testing.T) {
|
|
expectedErr := errors.New("database error")
|
|
stub := &repositoryStub{
|
|
FindManyByFilterFunc: func(ctx context.Context, _ builder.Query, decoder rd.DecodingFunc) error {
|
|
return expectedErr
|
|
},
|
|
}
|
|
|
|
store := &outboxStore{logger: logger, repo: stub}
|
|
results, err := store.ListPending(ctx, 10)
|
|
|
|
require.Error(t, err)
|
|
assert.Nil(t, results)
|
|
assert.Equal(t, expectedErr, err)
|
|
})
|
|
}
|
|
|
|
func TestOutboxStore_MarkSent(t *testing.T) {
|
|
ctx := context.Background()
|
|
logger := zap.NewNop()
|
|
eventRef := primitive.NewObjectID()
|
|
sentTime := time.Now()
|
|
|
|
t.Run("Success", func(t *testing.T) {
|
|
var patchedID primitive.ObjectID
|
|
stub := &repositoryStub{
|
|
PatchFunc: func(ctx context.Context, id primitive.ObjectID, _ repository.PatchDoc) error {
|
|
patchedID = id
|
|
return nil
|
|
},
|
|
}
|
|
|
|
store := &outboxStore{logger: logger, repo: stub}
|
|
err := store.MarkSent(ctx, eventRef, sentTime)
|
|
|
|
require.NoError(t, err)
|
|
assert.Equal(t, eventRef, patchedID)
|
|
})
|
|
|
|
t.Run("ZeroEventID", func(t *testing.T) {
|
|
stub := &repositoryStub{}
|
|
store := &outboxStore{logger: logger, repo: stub}
|
|
|
|
err := store.MarkSent(ctx, primitive.NilObjectID, sentTime)
|
|
|
|
require.Error(t, err)
|
|
assert.True(t, errors.Is(err, merrors.ErrInvalidArg))
|
|
})
|
|
|
|
t.Run("PatchError", func(t *testing.T) {
|
|
expectedErr := errors.New("database error")
|
|
stub := &repositoryStub{
|
|
PatchFunc: func(ctx context.Context, id primitive.ObjectID, _ repository.PatchDoc) error {
|
|
return expectedErr
|
|
},
|
|
}
|
|
|
|
store := &outboxStore{logger: logger, repo: stub}
|
|
err := store.MarkSent(ctx, eventRef, sentTime)
|
|
|
|
require.Error(t, err)
|
|
assert.Equal(t, expectedErr, err)
|
|
})
|
|
}
|
|
|
|
func TestOutboxStore_MarkFailed(t *testing.T) {
|
|
ctx := context.Background()
|
|
logger := zap.NewNop()
|
|
eventRef := primitive.NewObjectID()
|
|
|
|
t.Run("Success", func(t *testing.T) {
|
|
var patchedID primitive.ObjectID
|
|
stub := &repositoryStub{
|
|
PatchFunc: func(ctx context.Context, id primitive.ObjectID, _ repository.PatchDoc) error {
|
|
patchedID = id
|
|
return nil
|
|
},
|
|
}
|
|
|
|
store := &outboxStore{logger: logger, repo: stub}
|
|
err := store.MarkFailed(ctx, eventRef)
|
|
|
|
require.NoError(t, err)
|
|
assert.Equal(t, eventRef, patchedID)
|
|
})
|
|
|
|
t.Run("ZeroEventID", func(t *testing.T) {
|
|
stub := &repositoryStub{}
|
|
store := &outboxStore{logger: logger, repo: stub}
|
|
|
|
err := store.MarkFailed(ctx, primitive.NilObjectID)
|
|
|
|
require.Error(t, err)
|
|
assert.True(t, errors.Is(err, merrors.ErrInvalidArg))
|
|
})
|
|
|
|
t.Run("PatchError", func(t *testing.T) {
|
|
expectedErr := errors.New("database error")
|
|
stub := &repositoryStub{
|
|
PatchFunc: func(ctx context.Context, id primitive.ObjectID, _ repository.PatchDoc) error {
|
|
return expectedErr
|
|
},
|
|
}
|
|
|
|
store := &outboxStore{logger: logger, repo: stub}
|
|
err := store.MarkFailed(ctx, eventRef)
|
|
|
|
require.Error(t, err)
|
|
assert.Equal(t, expectedErr, err)
|
|
})
|
|
}
|
|
|
|
func TestOutboxStore_IncrementAttempts(t *testing.T) {
|
|
ctx := context.Background()
|
|
logger := zap.NewNop()
|
|
eventRef := primitive.NewObjectID()
|
|
|
|
t.Run("Success", func(t *testing.T) {
|
|
var patchedID primitive.ObjectID
|
|
stub := &repositoryStub{
|
|
PatchFunc: func(ctx context.Context, id primitive.ObjectID, _ repository.PatchDoc) error {
|
|
patchedID = id
|
|
return nil
|
|
},
|
|
}
|
|
|
|
store := &outboxStore{logger: logger, repo: stub}
|
|
err := store.IncrementAttempts(ctx, eventRef)
|
|
|
|
require.NoError(t, err)
|
|
assert.Equal(t, eventRef, patchedID)
|
|
})
|
|
|
|
t.Run("ZeroEventID", func(t *testing.T) {
|
|
stub := &repositoryStub{}
|
|
store := &outboxStore{logger: logger, repo: stub}
|
|
|
|
err := store.IncrementAttempts(ctx, primitive.NilObjectID)
|
|
|
|
require.Error(t, err)
|
|
assert.True(t, errors.Is(err, merrors.ErrInvalidArg))
|
|
})
|
|
|
|
t.Run("PatchError", func(t *testing.T) {
|
|
expectedErr := errors.New("database error")
|
|
stub := &repositoryStub{
|
|
PatchFunc: func(ctx context.Context, id primitive.ObjectID, _ repository.PatchDoc) error {
|
|
return expectedErr
|
|
},
|
|
}
|
|
|
|
store := &outboxStore{logger: logger, repo: stub}
|
|
err := store.IncrementAttempts(ctx, eventRef)
|
|
|
|
require.Error(t, err)
|
|
assert.Equal(t, expectedErr, err)
|
|
})
|
|
|
|
t.Run("MultipleIncrements", func(t *testing.T) {
|
|
var callCount int
|
|
stub := &repositoryStub{
|
|
PatchFunc: func(ctx context.Context, id primitive.ObjectID, _ repository.PatchDoc) error {
|
|
callCount++
|
|
return nil
|
|
},
|
|
}
|
|
|
|
store := &outboxStore{logger: logger, repo: stub}
|
|
|
|
// Simulate multiple retry attempts
|
|
for i := 0; i < 3; i++ {
|
|
err := store.IncrementAttempts(ctx, eventRef)
|
|
require.NoError(t, err)
|
|
}
|
|
|
|
assert.Equal(t, 3, callCount)
|
|
})
|
|
}
|