Files
sendico/api/ledger/storage/mongo/store/outbox_test.go
Stephan D 62a6631b9a
All checks were successful
ci/woodpecker/push/db Pipeline was successful
ci/woodpecker/push/nats Pipeline was successful
service backend
2025-11-07 18:35:26 +01:00

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