snapshot of https://github.com/ThreeDotsLabs/watermill.git (master, 2026-09-15; blobs > 131072B omitted)
Some checks failed
master / ci (push) Failing after 0s
Some checks failed
master / ci (push) Failing after 0s
This commit is contained in:
539
components/cqrs/event_processor_test.go
Normal file
539
components/cqrs/event_processor_test.go
Normal file
@@ -0,0 +1,539 @@
|
||||
package cqrs_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/ThreeDotsLabs/watermill/components/cqrs"
|
||||
"github.com/ThreeDotsLabs/watermill/message"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestEventProcessorConfig_Validate(t *testing.T) {
|
||||
testCases := []struct {
|
||||
Name string
|
||||
ModifyValidConfig func(*cqrs.EventProcessorConfig)
|
||||
ExpectedErr error
|
||||
}{
|
||||
{
|
||||
Name: "valid_config",
|
||||
ModifyValidConfig: nil,
|
||||
ExpectedErr: nil,
|
||||
},
|
||||
{
|
||||
Name: "missing_GenerateHandlerSubscribeTopic",
|
||||
ModifyValidConfig: func(config *cqrs.EventProcessorConfig) {
|
||||
config.GenerateSubscribeTopic = nil
|
||||
},
|
||||
ExpectedErr: fmt.Errorf("missing GenerateHandlerTopic"),
|
||||
},
|
||||
{
|
||||
Name: "missing_marshaler",
|
||||
ModifyValidConfig: func(config *cqrs.EventProcessorConfig) {
|
||||
config.Marshaler = nil
|
||||
},
|
||||
ExpectedErr: fmt.Errorf("missing Marshaler"),
|
||||
},
|
||||
{
|
||||
Name: "missing_subscriber_constructor",
|
||||
ModifyValidConfig: func(config *cqrs.EventProcessorConfig) {
|
||||
config.SubscriberConstructor = nil
|
||||
},
|
||||
ExpectedErr: fmt.Errorf("missing SubscriberConstructor"),
|
||||
},
|
||||
}
|
||||
for i := range testCases {
|
||||
tc := testCases[i]
|
||||
|
||||
t.Run(tc.Name, func(t *testing.T) {
|
||||
validConfig := cqrs.EventProcessorConfig{
|
||||
GenerateSubscribeTopic: func(params cqrs.EventProcessorGenerateSubscribeTopicParams) (string, error) {
|
||||
return "", nil
|
||||
},
|
||||
SubscriberConstructor: func(params cqrs.EventProcessorSubscriberConstructorParams) (message.Subscriber, error) {
|
||||
return nil, nil
|
||||
},
|
||||
Marshaler: cqrs.JSONMarshaler{},
|
||||
}
|
||||
|
||||
if tc.ModifyValidConfig != nil {
|
||||
tc.ModifyValidConfig(&validConfig)
|
||||
}
|
||||
|
||||
err := validConfig.Validate()
|
||||
if tc.ExpectedErr == nil {
|
||||
assert.NoError(t, err)
|
||||
} else {
|
||||
assert.EqualError(t, err, tc.ExpectedErr.Error())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewEventProcessor(t *testing.T) {
|
||||
eventConfig := cqrs.EventProcessorConfig{
|
||||
GenerateSubscribeTopic: func(params cqrs.EventProcessorGenerateSubscribeTopicParams) (string, error) {
|
||||
return "", nil
|
||||
},
|
||||
SubscriberConstructor: func(params cqrs.EventProcessorSubscriberConstructorParams) (message.Subscriber, error) {
|
||||
return nil, nil
|
||||
},
|
||||
Marshaler: cqrs.JSONMarshaler{},
|
||||
}
|
||||
require.NoError(t, eventConfig.Validate())
|
||||
|
||||
router, err := message.NewRouter(message.RouterConfig{}, nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
cp, err := cqrs.NewEventProcessorWithConfig(router, eventConfig)
|
||||
assert.NotNil(t, cp)
|
||||
assert.NoError(t, err)
|
||||
|
||||
eventConfig.SubscriberConstructor = nil
|
||||
require.Error(t, eventConfig.Validate())
|
||||
|
||||
cp, err = cqrs.NewEventProcessorWithConfig(router, eventConfig)
|
||||
assert.Nil(t, cp)
|
||||
assert.Error(t, err)
|
||||
}
|
||||
|
||||
type nonPointerEventProcessor struct {
|
||||
}
|
||||
|
||||
func (nonPointerEventProcessor) HandlerName() string {
|
||||
return "nonPointerEventProcessor"
|
||||
}
|
||||
|
||||
func (nonPointerEventProcessor) NewEvent() interface{} {
|
||||
return TestEvent{}
|
||||
}
|
||||
|
||||
func (nonPointerEventProcessor) Handle(ctx context.Context, cmd interface{}) error {
|
||||
panic("not implemented")
|
||||
}
|
||||
|
||||
func TestEventProcessor_non_pointer_event(t *testing.T) {
|
||||
ts := NewTestServices()
|
||||
|
||||
handler := nonPointerEventProcessor{}
|
||||
|
||||
router, err := message.NewRouter(message.RouterConfig{}, ts.Logger)
|
||||
require.NoError(t, err)
|
||||
|
||||
eventProcessor, err := cqrs.NewEventProcessorWithConfig(
|
||||
router,
|
||||
cqrs.EventProcessorConfig{
|
||||
GenerateSubscribeTopic: func(params cqrs.EventProcessorGenerateSubscribeTopicParams) (string, error) {
|
||||
return "", nil
|
||||
},
|
||||
SubscriberConstructor: func(params cqrs.EventProcessorSubscriberConstructorParams) (message.Subscriber, error) {
|
||||
return nil, nil
|
||||
},
|
||||
Marshaler: ts.Marshaler,
|
||||
Logger: ts.Logger,
|
||||
},
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = eventProcessor.AddHandlers(handler)
|
||||
assert.IsType(t, cqrs.NonPointerError{}, errors.Cause(err))
|
||||
}
|
||||
|
||||
type duplicateTestEventHandler1 struct{}
|
||||
|
||||
func (h duplicateTestEventHandler1) HandlerName() string {
|
||||
return "duplicateTestEventHandler1"
|
||||
}
|
||||
|
||||
func (duplicateTestEventHandler1) NewEvent() interface{} {
|
||||
return &TestEvent{}
|
||||
}
|
||||
|
||||
func (h *duplicateTestEventHandler1) Handle(ctx context.Context, event interface{}) error { return nil }
|
||||
|
||||
type duplicateTestEventHandler2 struct{}
|
||||
|
||||
func (h duplicateTestEventHandler2) HandlerName() string {
|
||||
return "duplicateTestEventHandler2"
|
||||
}
|
||||
|
||||
func (duplicateTestEventHandler2) NewEvent() interface{} {
|
||||
return &TestEvent{}
|
||||
}
|
||||
|
||||
func (h *duplicateTestEventHandler2) Handle(ctx context.Context, event interface{}) error { return nil }
|
||||
|
||||
func TestEventProcessor_multiple_same_event_handlers(t *testing.T) {
|
||||
ts := NewTestServices()
|
||||
|
||||
router, err := message.NewRouter(message.RouterConfig{}, ts.Logger)
|
||||
require.NoError(t, err)
|
||||
|
||||
eventProcessor, err := cqrs.NewEventProcessorWithConfig(
|
||||
router,
|
||||
cqrs.EventProcessorConfig{
|
||||
GenerateSubscribeTopic: func(params cqrs.EventProcessorGenerateSubscribeTopicParams) (string, error) {
|
||||
return "", nil
|
||||
},
|
||||
SubscriberConstructor: func(params cqrs.EventProcessorSubscriberConstructorParams) (message.Subscriber, error) {
|
||||
return nil, nil
|
||||
},
|
||||
Marshaler: ts.Marshaler,
|
||||
Logger: ts.Logger,
|
||||
},
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = eventProcessor.AddHandlers(
|
||||
&duplicateTestEventHandler1{},
|
||||
&duplicateTestEventHandler2{},
|
||||
)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestNewEventProcessor_OnHandle(t *testing.T) {
|
||||
ts := NewTestServices()
|
||||
|
||||
msg1, err := ts.Marshaler.Marshal(&TestEvent{ID: "1"})
|
||||
require.NoError(t, err)
|
||||
|
||||
msg2, err := ts.Marshaler.Marshal(&TestEvent{ID: "2"})
|
||||
require.NoError(t, err)
|
||||
|
||||
mockSub := &mockSubscriber{
|
||||
MessagesToSend: []*message.Message{
|
||||
msg1,
|
||||
msg2,
|
||||
},
|
||||
}
|
||||
|
||||
handlerCalled := 0
|
||||
|
||||
defer func() {
|
||||
// for msg 1 we are not calling handler - but returning before
|
||||
assert.Equal(t, 1, handlerCalled)
|
||||
}()
|
||||
|
||||
handler := cqrs.NewEventHandler("test", func(ctx context.Context, cmd *TestEvent) error {
|
||||
handlerCalled++
|
||||
return nil
|
||||
})
|
||||
|
||||
router, err := message.NewRouter(message.RouterConfig{}, ts.Logger)
|
||||
require.NoError(t, err)
|
||||
|
||||
onHandleCalled := int64(0)
|
||||
|
||||
config := cqrs.EventProcessorConfig{
|
||||
GenerateSubscribeTopic: func(params cqrs.EventProcessorGenerateSubscribeTopicParams) (string, error) {
|
||||
return "events", nil
|
||||
},
|
||||
SubscriberConstructor: func(params cqrs.EventProcessorSubscriberConstructorParams) (message.Subscriber, error) {
|
||||
return mockSub, nil
|
||||
},
|
||||
OnHandle: func(params cqrs.EventProcessorOnHandleParams) error {
|
||||
atomic.AddInt64(&onHandleCalled, 1)
|
||||
|
||||
assert.IsType(t, &TestEvent{}, params.Event)
|
||||
assert.Equal(t, "cqrs_test.TestEvent", params.EventName)
|
||||
assert.Equal(t, handler, params.Handler)
|
||||
|
||||
if params.Event.(*TestEvent).ID == "1" {
|
||||
assert.Equal(t, msg1, params.Message)
|
||||
return errors.New("test error")
|
||||
} else {
|
||||
assert.Equal(t, msg2, params.Message)
|
||||
}
|
||||
|
||||
return params.Handler.Handle(params.Message.Context(), params.Event)
|
||||
},
|
||||
Marshaler: ts.Marshaler,
|
||||
Logger: ts.Logger,
|
||||
}
|
||||
cp, err := cqrs.NewEventProcessorWithConfig(router, config)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = cp.AddHandlers(handler)
|
||||
require.NoError(t, err)
|
||||
|
||||
go func() {
|
||||
err := router.Run(context.Background())
|
||||
assert.NoError(t, err)
|
||||
}()
|
||||
|
||||
<-router.Running()
|
||||
|
||||
select {
|
||||
case <-msg1.Nacked():
|
||||
// ok
|
||||
case <-msg1.Acked():
|
||||
// ack received
|
||||
t.Fatal("ack received, message should be nacked")
|
||||
}
|
||||
|
||||
select {
|
||||
case <-msg2.Acked():
|
||||
// ok
|
||||
case <-msg2.Nacked():
|
||||
// nack received
|
||||
}
|
||||
|
||||
assert.EqualValues(t, 2, onHandleCalled)
|
||||
}
|
||||
|
||||
type UnknownEvent struct {
|
||||
}
|
||||
|
||||
func TestNewEventProcessor_AckOnUnknownEvent(t *testing.T) {
|
||||
ts := NewTestServices()
|
||||
|
||||
msg, err := ts.Marshaler.Marshal(&UnknownEvent{})
|
||||
require.NoError(t, err)
|
||||
|
||||
mockSub := &mockSubscriber{
|
||||
MessagesToSend: []*message.Message{
|
||||
msg,
|
||||
},
|
||||
}
|
||||
|
||||
router, err := message.NewRouter(message.RouterConfig{}, ts.Logger)
|
||||
require.NoError(t, err)
|
||||
|
||||
cp, err := cqrs.NewEventProcessorWithConfig(
|
||||
router,
|
||||
cqrs.EventProcessorConfig{
|
||||
GenerateSubscribeTopic: func(params cqrs.EventProcessorGenerateSubscribeTopicParams) (string, error) {
|
||||
return "events", nil
|
||||
},
|
||||
SubscriberConstructor: func(params cqrs.EventProcessorSubscriberConstructorParams) (message.Subscriber, error) {
|
||||
return mockSub, nil
|
||||
},
|
||||
AckOnUnknownEvent: true,
|
||||
Marshaler: ts.Marshaler,
|
||||
Logger: ts.Logger,
|
||||
},
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = cp.AddHandlers(
|
||||
cqrs.NewEventHandler("test", func(ctx context.Context, cmd *TestEvent) error {
|
||||
return nil
|
||||
}),
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
go func() {
|
||||
err := router.Run(context.Background())
|
||||
assert.NoError(t, err)
|
||||
}()
|
||||
|
||||
<-router.Running()
|
||||
|
||||
select {
|
||||
case <-msg.Acked():
|
||||
// ok
|
||||
case <-msg.Nacked():
|
||||
// ack received
|
||||
t.Fatal("ack received, message should be nacked")
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewEventProcessor_AckOnUnknownEvent_disabled(t *testing.T) {
|
||||
ts := NewTestServices()
|
||||
|
||||
msg, err := ts.Marshaler.Marshal(&UnknownEvent{})
|
||||
require.NoError(t, err)
|
||||
|
||||
mockSub := &mockSubscriber{
|
||||
MessagesToSend: []*message.Message{
|
||||
msg,
|
||||
},
|
||||
}
|
||||
|
||||
router, err := message.NewRouter(message.RouterConfig{}, ts.Logger)
|
||||
require.NoError(t, err)
|
||||
|
||||
cp, err := cqrs.NewEventProcessorWithConfig(
|
||||
router,
|
||||
cqrs.EventProcessorConfig{
|
||||
GenerateSubscribeTopic: func(params cqrs.EventProcessorGenerateSubscribeTopicParams) (string, error) {
|
||||
return "events", nil
|
||||
},
|
||||
SubscriberConstructor: func(params cqrs.EventProcessorSubscriberConstructorParams) (message.Subscriber, error) {
|
||||
return mockSub, nil
|
||||
},
|
||||
AckOnUnknownEvent: false,
|
||||
Marshaler: ts.Marshaler,
|
||||
Logger: ts.Logger,
|
||||
},
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = cp.AddHandlers(
|
||||
cqrs.NewEventHandler("test", func(ctx context.Context, cmd *TestEvent) error {
|
||||
return nil
|
||||
}),
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
go func() {
|
||||
err := router.Run(context.Background())
|
||||
assert.NoError(t, err)
|
||||
}()
|
||||
|
||||
<-router.Running()
|
||||
|
||||
select {
|
||||
case <-msg.Nacked():
|
||||
// ok
|
||||
case <-msg.Acked():
|
||||
// ack received
|
||||
t.Fatal("ack received, message should be nacked")
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewEventProcessor_backward_compatibility_of_AckOnUnknownEvent(t *testing.T) {
|
||||
ts := NewTestServices()
|
||||
|
||||
msg, err := ts.Marshaler.Marshal(&UnknownEvent{})
|
||||
require.NoError(t, err)
|
||||
|
||||
mockSub := &mockSubscriber{
|
||||
MessagesToSend: []*message.Message{
|
||||
msg,
|
||||
},
|
||||
}
|
||||
|
||||
cp, err := cqrs.NewEventProcessor(
|
||||
[]cqrs.EventHandler{
|
||||
cqrs.NewEventHandler("test", func(ctx context.Context, cmd *TestEvent) error {
|
||||
return nil
|
||||
}),
|
||||
},
|
||||
func(eventName string) string {
|
||||
return "events"
|
||||
},
|
||||
func(handlerName string) (message.Subscriber, error) {
|
||||
return mockSub, nil
|
||||
},
|
||||
ts.Marshaler,
|
||||
ts.Logger,
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
router, err := message.NewRouter(message.RouterConfig{}, ts.Logger)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = cp.AddHandlersToRouter(router)
|
||||
require.NoError(t, err)
|
||||
|
||||
go func() {
|
||||
err := router.Run(context.Background())
|
||||
assert.NoError(t, err)
|
||||
}()
|
||||
|
||||
<-router.Running()
|
||||
|
||||
select {
|
||||
case <-msg.Acked():
|
||||
// ok
|
||||
case <-msg.Nacked():
|
||||
// ack received
|
||||
t.Fatal("ack received, message should be nacked")
|
||||
}
|
||||
}
|
||||
|
||||
func TestEventProcessor_AddHandlersToRouter_without_disableRouterAutoAddHandlers(t *testing.T) {
|
||||
ts := NewTestServices()
|
||||
|
||||
router, err := message.NewRouter(message.RouterConfig{}, ts.Logger)
|
||||
require.NoError(t, err)
|
||||
|
||||
cp, err := cqrs.NewEventProcessorWithConfig(
|
||||
router,
|
||||
cqrs.EventProcessorConfig{
|
||||
GenerateSubscribeTopic: func(params cqrs.EventProcessorGenerateSubscribeTopicParams) (string, error) {
|
||||
return "events", nil
|
||||
},
|
||||
SubscriberConstructor: func(params cqrs.EventProcessorSubscriberConstructorParams) (message.Subscriber, error) {
|
||||
return ts.EventsPubSub, nil
|
||||
},
|
||||
AckOnUnknownEvent: false,
|
||||
Marshaler: ts.Marshaler,
|
||||
Logger: ts.Logger,
|
||||
},
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = cp.AddHandlersToRouter(router)
|
||||
assert.ErrorContains(t, err, "AddHandlersToRouter should be called only when using deprecated NewEventProcessor")
|
||||
}
|
||||
|
||||
func TestEventProcessor_original_msg_set_to_ctx(t *testing.T) {
|
||||
ts := NewTestServices()
|
||||
|
||||
msg, err := ts.Marshaler.Marshal(&TestEvent{})
|
||||
require.NoError(t, err)
|
||||
|
||||
mockSub := &mockSubscriber{
|
||||
MessagesToSend: []*message.Message{
|
||||
msg,
|
||||
},
|
||||
}
|
||||
|
||||
router, err := message.NewRouter(message.RouterConfig{}, ts.Logger)
|
||||
require.NoError(t, err)
|
||||
|
||||
cp, err := cqrs.NewEventProcessorWithConfig(
|
||||
router,
|
||||
cqrs.EventProcessorConfig{
|
||||
GenerateSubscribeTopic: func(params cqrs.EventProcessorGenerateSubscribeTopicParams) (string, error) {
|
||||
return "events", nil
|
||||
},
|
||||
SubscriberConstructor: func(params cqrs.EventProcessorSubscriberConstructorParams) (message.Subscriber, error) {
|
||||
return mockSub, nil
|
||||
},
|
||||
AckOnUnknownEvent: true,
|
||||
Marshaler: ts.Marshaler,
|
||||
Logger: ts.Logger,
|
||||
},
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
var msgFromCtx *message.Message
|
||||
|
||||
err = cp.AddHandlers(cqrs.NewEventHandler(
|
||||
"handler", func(ctx context.Context, cmd *TestEvent) error {
|
||||
msgFromCtx = cqrs.OriginalMessageFromCtx(ctx)
|
||||
return nil
|
||||
}),
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
go func() {
|
||||
err := router.Run(context.Background())
|
||||
assert.NoError(t, err)
|
||||
}()
|
||||
|
||||
<-router.Running()
|
||||
|
||||
select {
|
||||
case <-msg.Acked():
|
||||
// ok
|
||||
case <-msg.Nacked():
|
||||
// nack received
|
||||
t.Fatal("nack received, message should be acked")
|
||||
case <-time.After(1 * time.Second):
|
||||
t.Fatal("timeout waiting for ack")
|
||||
}
|
||||
|
||||
require.NotNil(t, msgFromCtx)
|
||||
assert.Equal(t, msg, msgFromCtx)
|
||||
}
|
||||
Reference in New Issue
Block a user