242 lines
6.8 KiB
Go
242 lines
6.8 KiB
Go
package middleware_test
|
|
|
|
import (
|
|
"context"
|
|
stdErrors "errors"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/pkg/errors"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
|
|
"github.com/ThreeDotsLabs/watermill"
|
|
"github.com/ThreeDotsLabs/watermill/message"
|
|
"github.com/ThreeDotsLabs/watermill/message/router/middleware"
|
|
"github.com/ThreeDotsLabs/watermill/message/subscriber"
|
|
"github.com/ThreeDotsLabs/watermill/pubsub/gochannel"
|
|
)
|
|
|
|
const topic = "testing_poison_queue_topic"
|
|
|
|
// TestPoisonQueue_handler_ok simulates the situation when the message is processed correctly
|
|
// We expect that all messages pass through the middleware unaffected and the poison queue catches no messages.
|
|
func TestPoisonQueue_handler_ok(t *testing.T) {
|
|
poisonPublisher := mockPublisher{behaviour: BehaviourAlwaysOK}
|
|
|
|
poisonQueue, err := middleware.PoisonQueue(&poisonPublisher, topic)
|
|
require.NoError(t, err)
|
|
|
|
poisonQueueWithFilter, err := middleware.PoisonQueueWithFilter(&poisonPublisher, topic, func(err error) bool {
|
|
return true
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
testCases := []struct {
|
|
Name string
|
|
Middleware message.HandlerMiddleware
|
|
}{
|
|
{
|
|
Name: "PoisonQueue",
|
|
Middleware: poisonQueue,
|
|
},
|
|
{
|
|
Name: "PoisonQueueWithFilter",
|
|
Middleware: poisonQueueWithFilter,
|
|
},
|
|
}
|
|
|
|
for _, c := range testCases {
|
|
t.Run(c.Name, func(t *testing.T) {
|
|
produced, err := c.Middleware(handlerFuncAlwaysOK)(
|
|
message.NewMessage("uuid", nil),
|
|
)
|
|
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, handlerFuncAlwaysOKMessages, produced)
|
|
assert.Empty(t, poisonPublisher.PopMessages())
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestPoisonQueue_handler_failing(t *testing.T) {
|
|
poisonPublisher := mockPublisher{behaviour: BehaviourAlwaysOK}
|
|
|
|
poisonQueue, err := middleware.PoisonQueue(&poisonPublisher, topic)
|
|
require.NoError(t, err)
|
|
|
|
poisonQueueWithFilter, err := middleware.PoisonQueueWithFilter(&poisonPublisher, topic, func(err error) bool {
|
|
return true
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
testCases := []struct {
|
|
Name string
|
|
Middleware message.HandlerMiddleware
|
|
}{
|
|
{
|
|
Name: "PoisonQueue",
|
|
Middleware: poisonQueue,
|
|
},
|
|
{
|
|
Name: "PoisonQueueWithFilter",
|
|
Middleware: poisonQueueWithFilter,
|
|
},
|
|
}
|
|
|
|
for _, c := range testCases {
|
|
t.Run(c.Name, func(t *testing.T) {
|
|
msg := message.NewMessage("uuid", []byte("payload"))
|
|
produced, err := c.Middleware(handlerFuncAlwaysFailing)(
|
|
msg,
|
|
)
|
|
|
|
// the middleware itself should not fail; the publisher is working OK, so no error is passed down the chain
|
|
assert.NoError(t, err)
|
|
|
|
// but no messages should be passed
|
|
assert.Empty(t, produced)
|
|
|
|
// the original message should end up in the poison queue
|
|
poisonMsgs := poisonPublisher.PopMessages()
|
|
require.Len(t, poisonMsgs, 1)
|
|
|
|
assert.Equal(t, msg.Payload, poisonMsgs[0].Payload)
|
|
|
|
// there should be additional metadata telling why the message was poisoned
|
|
// it should be the error that the handler failed with
|
|
assert.Equal(t, errFailed.Error(), poisonMsgs[0].Metadata.Get(middleware.ReasonForPoisonedKey))
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestPoisonQueue_context_values(t *testing.T) {
|
|
pubSub := gochannel.NewGoChannel(
|
|
gochannel.Config{Persistent: true},
|
|
watermill.NewStdLogger(true, true),
|
|
)
|
|
|
|
logger := watermill.NewStdLogger(true, true)
|
|
|
|
router, err := message.NewRouter(message.RouterConfig{}, logger)
|
|
require.NoError(t, err)
|
|
|
|
pq, err := middleware.PoisonQueue(pubSub, "poison_queue")
|
|
require.NoError(t, err)
|
|
router.AddMiddleware(pq)
|
|
|
|
router.AddConsumerHandler("handler_name", "test", pubSub, func(msg *message.Message) error {
|
|
return errors.New("error")
|
|
})
|
|
|
|
go func() {
|
|
require.NoError(t, router.Run(context.Background()))
|
|
}()
|
|
require.NoError(t, err)
|
|
defer func() { _ = router.Close() }()
|
|
|
|
select {
|
|
case <-router.Running():
|
|
// ok
|
|
case <-time.After(time.Second):
|
|
t.Fatal("waiting for router timeout")
|
|
}
|
|
|
|
err = pubSub.Publish("test", message.NewMessage("1", nil))
|
|
require.NoError(t, err)
|
|
|
|
msgs, err := pubSub.Subscribe(context.Background(), "poison_queue")
|
|
require.NoError(t, err)
|
|
|
|
messages, all := subscriber.BulkRead(msgs, 1, time.Second)
|
|
require.True(t, all, "no messages received")
|
|
|
|
assert.Equal(t, "handler_name", messages[0].Metadata[middleware.PoisonedHandlerKey])
|
|
assert.Equal(t, "gochannel.GoChannel", messages[0].Metadata[middleware.PoisonedSubscriberKey])
|
|
assert.Equal(t, "test", messages[0].Metadata[middleware.PoisonedTopicKey])
|
|
assert.Equal(t, "error", messages[0].Metadata[middleware.ReasonForPoisonedKey])
|
|
}
|
|
|
|
func TestPoisonQueue_handler_failing_publisher_failing(t *testing.T) {
|
|
poisonPublisher := mockPublisher{behaviour: BehaviourAlwaysFail}
|
|
|
|
poisonQueue, err := middleware.PoisonQueue(&poisonPublisher, topic)
|
|
require.NoError(t, err)
|
|
|
|
poisonQueueWithFilter, err := middleware.PoisonQueueWithFilter(&poisonPublisher, topic, func(err error) bool {
|
|
return true
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
testCases := []struct {
|
|
Name string
|
|
Middleware message.HandlerMiddleware
|
|
}{
|
|
{
|
|
Name: "PoisonQueue",
|
|
Middleware: poisonQueue,
|
|
},
|
|
{
|
|
Name: "PoisonQueueWithFilter",
|
|
Middleware: poisonQueueWithFilter,
|
|
},
|
|
}
|
|
|
|
for _, c := range testCases {
|
|
t.Run(c.Name, func(t *testing.T) {
|
|
msg := message.NewMessage("uuid", nil)
|
|
produced, err := poisonQueue(handlerFuncAlwaysFailing)(
|
|
msg,
|
|
)
|
|
|
|
// publisher failed, can't hide the error anymore
|
|
// Instead of checking the specific error, we check if the error.Is() is the same as the one we expect
|
|
assert.ErrorIs(t, err, errFailed)
|
|
|
|
// can't really expect any produced messages
|
|
assert.Empty(t, produced)
|
|
|
|
// nor poison messages
|
|
assert.Empty(t, poisonPublisher.PopMessages())
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestPoisonQueueWithFilter_poison_queue(t *testing.T) {
|
|
poisonPublisher := mockPublisher{behaviour: BehaviourAlwaysOK}
|
|
|
|
poisonQueueErr := errors.New("poison queue err")
|
|
msg := message.NewMessage("uuid", []byte("payload"))
|
|
|
|
poisonQueue, err := middleware.PoisonQueueWithFilter(&poisonPublisher, topic, func(err error) bool {
|
|
return stdErrors.Is(err, poisonQueueErr)
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
_, err = poisonQueue(func(msg *message.Message) (messages []*message.Message, e error) {
|
|
return nil, poisonQueueErr
|
|
})(msg)
|
|
|
|
assert.NoError(t, err)
|
|
require.Len(t, poisonPublisher.PopMessages(), 1)
|
|
}
|
|
|
|
func TestPoisonQueueWithFilter_non_poison_queue(t *testing.T) {
|
|
poisonPublisher := mockPublisher{behaviour: BehaviourAlwaysOK}
|
|
|
|
nonPoisonQueueErr := errors.New("non poison queue err")
|
|
msg := message.NewMessage("uuid", []byte("payload"))
|
|
|
|
poisonQueue, err := middleware.PoisonQueueWithFilter(&poisonPublisher, topic, func(err error) bool {
|
|
return !stdErrors.Is(err, nonPoisonQueueErr)
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
_, err = poisonQueue(func(msg *message.Message) (messages []*message.Message, e error) {
|
|
return nil, nonPoisonQueueErr
|
|
})(msg)
|
|
|
|
assert.Error(t, err)
|
|
require.Len(t, poisonPublisher.PopMessages(), 0)
|
|
}
|