1504 lines
34 KiB
Go
1504 lines
34 KiB
Go
package message_test
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"sync"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/pkg/errors"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
|
|
"github.com/ThreeDotsLabs/watermill"
|
|
"github.com/ThreeDotsLabs/watermill/internal"
|
|
"github.com/ThreeDotsLabs/watermill/message"
|
|
"github.com/ThreeDotsLabs/watermill/message/subscriber"
|
|
"github.com/ThreeDotsLabs/watermill/pubsub/gochannel"
|
|
"github.com/ThreeDotsLabs/watermill/pubsub/tests"
|
|
)
|
|
|
|
func TestRouter_functional(t *testing.T) {
|
|
testID := watermill.NewUUID()
|
|
subscribeTopic := "test_topic_" + testID
|
|
|
|
pub, sub := createPubSub()
|
|
defer func() {
|
|
assert.NoError(t, pub.Close())
|
|
assert.NoError(t, sub.Close())
|
|
}()
|
|
|
|
messagesCount := 50
|
|
|
|
var expectedReceivedMessages message.Messages
|
|
allMessagesSent := make(chan struct{})
|
|
go func() {
|
|
expectedReceivedMessages = publishMessagesForHandler(t, messagesCount, pub, sub, subscribeTopic)
|
|
allMessagesSent <- struct{}{}
|
|
}()
|
|
|
|
receivedMessagesCh1 := make(chan *message.Message, messagesCount)
|
|
receivedMessagesCh2 := make(chan *message.Message, messagesCount)
|
|
sentByHandlerCh := make(chan *message.Message, messagesCount)
|
|
|
|
publishedEventsTopic := "published_events_" + testID
|
|
publishedByHandlerCh, err := sub.Subscribe(context.Background(), publishedEventsTopic)
|
|
|
|
var publishedByHandler message.Messages
|
|
allPublishedByHandler := make(chan struct{})
|
|
|
|
go func() {
|
|
var all bool
|
|
publishedByHandler, all = subscriber.BulkRead(publishedByHandlerCh, messagesCount, time.Second*10)
|
|
assert.True(t, all)
|
|
allPublishedByHandler <- struct{}{}
|
|
}()
|
|
|
|
require.NoError(t, err)
|
|
|
|
r, err := message.NewRouter(
|
|
message.RouterConfig{},
|
|
watermill.NewStdLogger(true, true),
|
|
)
|
|
require.NoError(t, err)
|
|
|
|
r.AddHandler(
|
|
"test_subscriber_1",
|
|
subscribeTopic,
|
|
sub,
|
|
publishedEventsTopic,
|
|
pub,
|
|
func(msg *message.Message) (producedMessages []*message.Message, err error) {
|
|
receivedMessagesCh1 <- msg
|
|
|
|
toPublish := message.NewMessage(watermill.NewUUID(), nil)
|
|
sentByHandlerCh <- toPublish
|
|
|
|
return []*message.Message{toPublish}, nil
|
|
},
|
|
)
|
|
|
|
r.AddConsumerHandler(
|
|
"test_subscriber_2",
|
|
subscribeTopic,
|
|
sub,
|
|
func(msg *message.Message) error {
|
|
receivedMessagesCh2 <- msg
|
|
return nil
|
|
},
|
|
)
|
|
|
|
go func() {
|
|
assert.False(t, r.IsRunning())
|
|
assert.NoError(t, r.Run(context.Background()))
|
|
}()
|
|
<-r.Running()
|
|
|
|
defer func() {
|
|
assert.True(t, r.IsRunning())
|
|
assert.NoError(t, r.Close())
|
|
|
|
assert.True(t, r.IsClosed())
|
|
}()
|
|
|
|
<-allMessagesSent
|
|
|
|
expectedSentByHandler, all := readMessages(sentByHandlerCh, len(expectedReceivedMessages), time.Second*10)
|
|
assert.True(t, all)
|
|
|
|
receivedMessages1, all := subscriber.BulkRead(receivedMessagesCh1, len(expectedReceivedMessages), time.Second*10)
|
|
assert.True(t, all)
|
|
tests.AssertAllMessagesReceived(t, expectedReceivedMessages, receivedMessages1)
|
|
|
|
receivedMessages2, all := subscriber.BulkRead(receivedMessagesCh2, len(expectedReceivedMessages), time.Second*10)
|
|
assert.True(t, all)
|
|
tests.AssertAllMessagesReceived(t, expectedReceivedMessages, receivedMessages2)
|
|
|
|
<-allPublishedByHandler
|
|
tests.AssertAllMessagesReceived(t, expectedSentByHandler, publishedByHandler)
|
|
}
|
|
|
|
func TestRouter_functional_nack(t *testing.T) {
|
|
pub, sub := createPubSub()
|
|
defer func() {
|
|
assert.NoError(t, pub.Close())
|
|
assert.NoError(t, sub.Close())
|
|
}()
|
|
|
|
r, err := message.NewRouter(
|
|
message.RouterConfig{},
|
|
watermill.NewStdLogger(true, true),
|
|
)
|
|
require.NoError(t, err)
|
|
|
|
nackSend := make(chan struct{})
|
|
messageReceived := make(chan *message.Message, 2)
|
|
|
|
r.AddConsumerHandler(
|
|
"test_subscriber_1",
|
|
"subscribe_topic",
|
|
sub,
|
|
func(msg *message.Message) error {
|
|
messageReceived <- msg
|
|
|
|
if !internal.IsChannelClosed(nackSend) {
|
|
msg.Nack()
|
|
close(nackSend)
|
|
}
|
|
|
|
return nil
|
|
},
|
|
)
|
|
|
|
go func() {
|
|
require.NoError(t, r.Run(context.Background()))
|
|
}()
|
|
defer func() {
|
|
assert.NoError(t, r.Close())
|
|
}()
|
|
|
|
<-r.Running()
|
|
|
|
publishedMsg := message.NewMessage("1", nil)
|
|
require.NoError(t, pub.Publish("subscribe_topic", publishedMsg))
|
|
|
|
messages, all := subscriber.BulkRead(messageReceived, 2, time.Second)
|
|
assert.True(t, all, "not all messages received, probably not ack received, received %d", len(messages))
|
|
|
|
tests.AssertAllMessagesReceived(t, []*message.Message{publishedMsg, publishedMsg}, messages)
|
|
}
|
|
|
|
func TestRouter_ack_on_publishing_success(t *testing.T) {
|
|
publisher := &failingPublisherMock{
|
|
shouldPanic: false,
|
|
shouldError: false,
|
|
}
|
|
subscriber := &subscriberMock{
|
|
messages: make(chan *message.Message),
|
|
}
|
|
router, err := message.NewRouter(message.RouterConfig{
|
|
CloseTimeout: time.Second,
|
|
}, watermill.NewStdLogger(true, true))
|
|
require.NoError(t, err)
|
|
|
|
handlerFunc := func(msg *message.Message) ([]*message.Message, error) {
|
|
return message.Messages{msg}, nil
|
|
}
|
|
|
|
topic := "ack_on_publishing_success"
|
|
router.AddHandler(
|
|
"ack_on_publishing_success_handler",
|
|
topic,
|
|
subscriber,
|
|
topic,
|
|
publisher,
|
|
handlerFunc,
|
|
)
|
|
go func() {
|
|
err := router.Run(context.Background())
|
|
require.NoError(t, err)
|
|
}()
|
|
<-router.Running()
|
|
|
|
msg := message.NewMessage("uuid", []byte{})
|
|
subscriber.messages <- msg
|
|
|
|
select {
|
|
case <-msg.Acked():
|
|
// ok
|
|
case <-msg.Nacked():
|
|
t.Fatal("did not expect the message to be nacked")
|
|
case <-time.After(5 * time.Second):
|
|
t.Fatal("expected the message to be acked")
|
|
}
|
|
|
|
err = router.Close()
|
|
require.NoError(t, err)
|
|
}
|
|
|
|
func TestRouter_nack_on_publishing_failure(t *testing.T) {
|
|
publisher := &failingPublisherMock{
|
|
shouldPanic: false,
|
|
shouldError: true,
|
|
}
|
|
subscriber := &subscriberMock{
|
|
messages: make(chan *message.Message),
|
|
}
|
|
router, err := message.NewRouter(message.RouterConfig{
|
|
CloseTimeout: time.Second,
|
|
}, watermill.NewStdLogger(true, true))
|
|
require.NoError(t, err)
|
|
|
|
handlerFunc := func(msg *message.Message) ([]*message.Message, error) {
|
|
return message.Messages{msg}, nil
|
|
}
|
|
|
|
topic := "nack_on_publishing_failure"
|
|
router.AddHandler(
|
|
"nack_on_publishing_failure_handler",
|
|
topic,
|
|
subscriber,
|
|
topic,
|
|
publisher,
|
|
handlerFunc,
|
|
)
|
|
go func() {
|
|
err := router.Run(context.Background())
|
|
require.NoError(t, err)
|
|
}()
|
|
<-router.Running()
|
|
|
|
msg := message.NewMessage("uuid", []byte{})
|
|
subscriber.messages <- msg
|
|
|
|
select {
|
|
case <-msg.Acked():
|
|
t.Fatal("did not expect the message to be acked")
|
|
case <-msg.Nacked():
|
|
// ok
|
|
case <-time.After(5 * time.Second):
|
|
t.Fatal("expected the message to be nacked")
|
|
}
|
|
|
|
err = router.Close()
|
|
require.NoError(t, err)
|
|
}
|
|
|
|
func TestRouter_nack_on_panic(t *testing.T) {
|
|
publisher := &failingPublisherMock{
|
|
shouldPanic: true,
|
|
shouldError: false,
|
|
}
|
|
subscriber := &subscriberMock{
|
|
messages: make(chan *message.Message),
|
|
}
|
|
router, err := message.NewRouter(message.RouterConfig{
|
|
CloseTimeout: time.Second,
|
|
}, watermill.NewStdLogger(true, true))
|
|
require.NoError(t, err)
|
|
|
|
handlerFunc := func(msg *message.Message) ([]*message.Message, error) {
|
|
return message.Messages{msg}, nil
|
|
}
|
|
|
|
topic := "nack_on_panic"
|
|
router.AddHandler(
|
|
"nack_on_panic_handler",
|
|
topic,
|
|
subscriber,
|
|
topic,
|
|
publisher,
|
|
handlerFunc,
|
|
)
|
|
go func() {
|
|
err := router.Run(context.Background())
|
|
require.NoError(t, err)
|
|
}()
|
|
<-router.Running()
|
|
|
|
msg := message.NewMessage("uuid", []byte{})
|
|
subscriber.messages <- msg
|
|
|
|
select {
|
|
case <-msg.Acked():
|
|
t.Fatal("did not expect the message to be acked")
|
|
case <-msg.Nacked():
|
|
// ok
|
|
case <-time.After(5 * time.Second):
|
|
t.Fatal("expected the message to be nacked")
|
|
}
|
|
|
|
err = router.Close()
|
|
require.NoError(t, err)
|
|
}
|
|
|
|
func TestRouter_nack_on_handler_failure(t *testing.T) {
|
|
publisher := &failingPublisherMock{
|
|
shouldPanic: false,
|
|
shouldError: false,
|
|
}
|
|
subscriber := &subscriberMock{
|
|
messages: make(chan *message.Message),
|
|
}
|
|
router, err := message.NewRouter(message.RouterConfig{
|
|
CloseTimeout: time.Second,
|
|
}, watermill.NewStdLogger(true, true))
|
|
require.NoError(t, err)
|
|
|
|
handlerFunc := func(msg *message.Message) ([]*message.Message, error) {
|
|
return nil, errors.New("handler error")
|
|
}
|
|
|
|
topic := "nack_on_handler_failure"
|
|
router.AddHandler(
|
|
"nack_on_handler_failure_handler",
|
|
topic,
|
|
subscriber,
|
|
topic,
|
|
publisher,
|
|
handlerFunc,
|
|
)
|
|
go func() {
|
|
err := router.Run(context.Background())
|
|
require.NoError(t, err)
|
|
}()
|
|
<-router.Running()
|
|
|
|
msg := message.NewMessage("uuid", []byte{})
|
|
subscriber.messages <- msg
|
|
|
|
select {
|
|
case <-msg.Acked():
|
|
t.Fatal("did not expect the message to be acked")
|
|
case <-msg.Nacked():
|
|
// ok
|
|
case <-time.After(5 * time.Second):
|
|
t.Fatal("expected the message to be nacked")
|
|
}
|
|
}
|
|
|
|
func TestRouter_AddMiddleware_to_router(t *testing.T) {
|
|
pub, sub := createPubSub()
|
|
defer func() {
|
|
assert.NoError(t, pub.Close())
|
|
assert.NoError(t, sub.Close())
|
|
}()
|
|
router, err := message.NewRouter(message.RouterConfig{
|
|
CloseTimeout: time.Second,
|
|
}, watermill.NewStdLogger(true, true))
|
|
require.NoError(t, err)
|
|
|
|
middlewareCount := 3
|
|
middlewareCh := make(chan string, middlewareCount)
|
|
allMiddlewareExecuted := make(chan struct{}, 1)
|
|
var executedMiddleware []string
|
|
|
|
handlerFunc := func(msg *message.Message) ([]*message.Message, error) {
|
|
return message.Messages{msg}, nil
|
|
}
|
|
firstMiddleware := func(h message.HandlerFunc) message.HandlerFunc {
|
|
middlewareCh <- "firstMiddleware"
|
|
return h
|
|
}
|
|
|
|
secondMiddleware := func(h message.HandlerFunc) message.HandlerFunc {
|
|
middlewareCh <- "secondMiddleware"
|
|
return h
|
|
}
|
|
|
|
thirdMiddleware := func(h message.HandlerFunc) message.HandlerFunc {
|
|
middlewareCh <- "thirdMiddleware"
|
|
return h
|
|
}
|
|
|
|
topic := "some_topic"
|
|
router.AddMiddleware(firstMiddleware)
|
|
router.AddHandler(
|
|
"some_topic_handler",
|
|
topic,
|
|
sub,
|
|
topic,
|
|
pub,
|
|
handlerFunc,
|
|
)
|
|
router.AddMiddleware(secondMiddleware)
|
|
router.AddMiddleware(thirdMiddleware)
|
|
|
|
publishDone := make(chan struct{})
|
|
go func() {
|
|
defer close(publishDone)
|
|
msg := message.NewMessage(watermill.NewUUID(), []byte("test_payload"))
|
|
err := pub.Publish(topic, msg)
|
|
require.NoError(t, err)
|
|
}()
|
|
|
|
go func() {
|
|
for middlewareName := range middlewareCh {
|
|
executedMiddleware = append(executedMiddleware, middlewareName)
|
|
|
|
if len(executedMiddleware) == 3 {
|
|
allMiddlewareExecuted <- struct{}{}
|
|
break
|
|
}
|
|
}
|
|
allMiddlewareExecuted <- struct{}{}
|
|
}()
|
|
|
|
go func() {
|
|
err := router.Run(context.Background())
|
|
require.NoError(t, err)
|
|
}()
|
|
<-router.Running()
|
|
<-allMiddlewareExecuted
|
|
<-publishDone
|
|
|
|
err = router.Close()
|
|
require.NoError(t, err)
|
|
|
|
require.Equal(t, "firstMiddleware", executedMiddleware[2])
|
|
require.Equal(t, "secondMiddleware", executedMiddleware[1])
|
|
require.Equal(t, "thirdMiddleware", executedMiddleware[0])
|
|
}
|
|
|
|
func TestRouter_AddMiddleware_to_handler(t *testing.T) {
|
|
pub, sub := createPubSub()
|
|
defer func() {
|
|
assert.NoError(t, pub.Close())
|
|
assert.NoError(t, sub.Close())
|
|
}()
|
|
router, err := message.NewRouter(message.RouterConfig{
|
|
CloseTimeout: time.Second,
|
|
}, watermill.NewStdLogger(true, true))
|
|
require.NoError(t, err)
|
|
|
|
middlewareCount := 4
|
|
middlewareCh := make(chan string, middlewareCount)
|
|
allMiddlewareExecuted := make(chan struct{}, 1)
|
|
var executedMiddleware []string
|
|
|
|
handlerFunc := func(msg *message.Message) ([]*message.Message, error) {
|
|
return message.Messages{msg}, nil
|
|
}
|
|
firstMiddleware := func(h message.HandlerFunc) message.HandlerFunc {
|
|
middlewareCh <- "firstMiddleware"
|
|
return h
|
|
}
|
|
|
|
secondMiddleware := func(h message.HandlerFunc) message.HandlerFunc {
|
|
middlewareCh <- "secondMiddleware"
|
|
return h
|
|
}
|
|
|
|
thirdMiddleware := func(h message.HandlerFunc) message.HandlerFunc {
|
|
middlewareCh <- "thirdMiddleware"
|
|
return h
|
|
}
|
|
|
|
fourthMiddleware := func(h message.HandlerFunc) message.HandlerFunc {
|
|
middlewareCh <- "fourthMiddleware"
|
|
return h
|
|
}
|
|
|
|
topic := "some_topic"
|
|
router.AddMiddleware(firstMiddleware)
|
|
router.AddHandler(
|
|
"some_topic_handler",
|
|
topic,
|
|
sub,
|
|
topic,
|
|
pub,
|
|
handlerFunc,
|
|
).AddMiddleware(secondMiddleware, thirdMiddleware)
|
|
router.AddMiddleware(fourthMiddleware)
|
|
|
|
go func() {
|
|
for middlewareName := range middlewareCh {
|
|
executedMiddleware = append(executedMiddleware, middlewareName)
|
|
|
|
if len(executedMiddleware) == middlewareCount {
|
|
allMiddlewareExecuted <- struct{}{}
|
|
break
|
|
}
|
|
}
|
|
allMiddlewareExecuted <- struct{}{}
|
|
}()
|
|
|
|
go func() {
|
|
err := router.Run(context.Background())
|
|
require.NoError(t, err)
|
|
}()
|
|
<-router.Running()
|
|
<-allMiddlewareExecuted
|
|
|
|
err = router.Close()
|
|
require.NoError(t, err)
|
|
|
|
require.Equal(t, "firstMiddleware", executedMiddleware[3])
|
|
require.Equal(t, "secondMiddleware", executedMiddleware[2])
|
|
require.Equal(t, "thirdMiddleware", executedMiddleware[1])
|
|
require.Equal(t, "fourthMiddleware", executedMiddleware[0])
|
|
}
|
|
|
|
func TestRouter_AddMiddleware_to_handler_many(t *testing.T) {
|
|
pub, sub := createPubSub()
|
|
defer func() {
|
|
assert.NoError(t, pub.Close())
|
|
assert.NoError(t, sub.Close())
|
|
}()
|
|
router, err := message.NewRouter(message.RouterConfig{
|
|
CloseTimeout: time.Second,
|
|
}, watermill.NewStdLogger(true, true))
|
|
require.NoError(t, err)
|
|
|
|
middlewareCount := 6
|
|
middlewareCh := make(chan string, middlewareCount)
|
|
allMiddlewareExecuted := make(chan struct{}, 1)
|
|
var executedMiddleware []string
|
|
|
|
handlerFunc := func(msg *message.Message) ([]*message.Message, error) {
|
|
return message.Messages{msg}, nil
|
|
}
|
|
firstMiddleware := func(h message.HandlerFunc) message.HandlerFunc {
|
|
middlewareCh <- "firstMiddleware"
|
|
return h
|
|
}
|
|
|
|
secondMiddleware := func(h message.HandlerFunc) message.HandlerFunc {
|
|
middlewareCh <- "secondMiddleware"
|
|
return h
|
|
}
|
|
|
|
thirdMiddleware := func(h message.HandlerFunc) message.HandlerFunc {
|
|
middlewareCh <- "thirdMiddleware"
|
|
return h
|
|
}
|
|
|
|
fourthMiddleware := func(h message.HandlerFunc) message.HandlerFunc {
|
|
middlewareCh <- "fourthMiddleware"
|
|
return h
|
|
}
|
|
|
|
topic := "some_topic"
|
|
router.AddMiddleware(firstMiddleware)
|
|
router.AddHandler(
|
|
"some_topic_handler",
|
|
topic,
|
|
sub,
|
|
topic,
|
|
pub,
|
|
handlerFunc,
|
|
).AddMiddleware(secondMiddleware)
|
|
router.AddHandler(
|
|
"some_other_topic_handler",
|
|
"some_other_topic",
|
|
sub,
|
|
"some_other_topic",
|
|
pub,
|
|
func(msg *message.Message) ([]*message.Message, error) {
|
|
return message.Messages{msg}, nil
|
|
},
|
|
).AddMiddleware(thirdMiddleware)
|
|
router.AddMiddleware(fourthMiddleware)
|
|
|
|
go func() {
|
|
for middlewareName := range middlewareCh {
|
|
executedMiddleware = append(executedMiddleware, middlewareName)
|
|
|
|
if len(executedMiddleware) == middlewareCount {
|
|
allMiddlewareExecuted <- struct{}{}
|
|
break
|
|
}
|
|
}
|
|
allMiddlewareExecuted <- struct{}{}
|
|
}()
|
|
|
|
go func() {
|
|
err := router.Run(context.Background())
|
|
require.NoError(t, err)
|
|
}()
|
|
<-router.Running()
|
|
<-allMiddlewareExecuted
|
|
|
|
err = router.Close()
|
|
require.NoError(t, err)
|
|
|
|
require.Equal(t, 6, len(executedMiddleware))
|
|
counts := map[string]int{}
|
|
for _, m := range executedMiddleware {
|
|
counts[m] += 1
|
|
}
|
|
require.Equal(t, 2, counts["firstMiddleware"])
|
|
require.Equal(t, 1, counts["secondMiddleware"])
|
|
require.Equal(t, 1, counts["thirdMiddleware"])
|
|
require.Equal(t, 2, counts["fourthMiddleware"])
|
|
}
|
|
|
|
func TestRouter_RunHandlers(t *testing.T) {
|
|
ctx := context.Background()
|
|
|
|
testID := watermill.NewUUID()
|
|
subscribeTopic := "test_topic_" + testID
|
|
|
|
pubsub := gochannel.NewGoChannel(
|
|
gochannel.Config{Persistent: true},
|
|
watermill.NewStdLogger(true, true),
|
|
)
|
|
defer func() {
|
|
assert.NoError(t, pubsub.Close())
|
|
}()
|
|
|
|
r, err := message.NewRouter(
|
|
message.RouterConfig{},
|
|
watermill.NewStdLogger(true, true),
|
|
)
|
|
require.NoError(t, err)
|
|
|
|
defer func() {
|
|
assert.NoError(t, r.Close())
|
|
}()
|
|
|
|
go func() {
|
|
require.NoError(t, r.Run(ctx))
|
|
}()
|
|
<-r.Running()
|
|
|
|
messagesCount := 3
|
|
|
|
var expectedReceivedMessages message.Messages
|
|
|
|
receivedMessagesCh := make(chan *message.Message, messagesCount)
|
|
|
|
handler := r.AddConsumerHandler(
|
|
"test_subscriber_1",
|
|
subscribeTopic,
|
|
pubsub,
|
|
func(msg *message.Message) error {
|
|
receivedMessagesCh <- msg
|
|
return nil
|
|
},
|
|
)
|
|
require.NotNil(t, handler)
|
|
require.NoError(t, err)
|
|
|
|
require.NoError(t, r.RunHandlers(ctx))
|
|
require.NoError(t, r.RunHandlers(ctx)) // RunHandlers should be idempotent
|
|
|
|
select {
|
|
case <-handler.Started():
|
|
// ok
|
|
case <-time.After(time.Second):
|
|
t.Fatal("timeout waiting for handler")
|
|
}
|
|
|
|
expectedReceivedMessages = publishMessagesForHandler(t, messagesCount, pubsub, pubsub, subscribeTopic)
|
|
|
|
receivedMessages1, all := subscriber.BulkRead(receivedMessagesCh, len(expectedReceivedMessages), time.Second*10)
|
|
assert.True(t, all)
|
|
tests.AssertAllMessagesReceived(t, expectedReceivedMessages, receivedMessages1)
|
|
}
|
|
|
|
func TestRouter_close_handler(t *testing.T) {
|
|
testID := watermill.NewUUID()
|
|
subscribeTopic1 := "test_topic_1_" + testID
|
|
subscribeTopic2 := "test_topic_2_" + testID
|
|
|
|
pub, sub := createPubSub()
|
|
defer func() {
|
|
assert.NoError(t, pub.Close())
|
|
assert.NoError(t, sub.Close())
|
|
}()
|
|
|
|
r, err := message.NewRouter(
|
|
message.RouterConfig{},
|
|
watermill.NewStdLogger(true, true),
|
|
)
|
|
require.NoError(t, err)
|
|
|
|
messagesCount := 3
|
|
var expectedReceivedMessages message.Messages
|
|
receivedMessagesCh1 := make(chan *message.Message, messagesCount)
|
|
|
|
handler := r.AddConsumerHandler(
|
|
"test_subscriber_1",
|
|
subscribeTopic1,
|
|
sub,
|
|
func(msg *message.Message) error {
|
|
receivedMessagesCh1 <- msg
|
|
return nil
|
|
},
|
|
)
|
|
|
|
// to keep at least one running handler to prevent router from closing
|
|
r.AddConsumerHandler(
|
|
"noop_handler",
|
|
watermill.NewUUID(),
|
|
sub,
|
|
func(msg *message.Message) error {
|
|
return nil
|
|
},
|
|
)
|
|
|
|
go func() {
|
|
require.NoError(t, r.Run(context.Background()))
|
|
}()
|
|
<-r.Running()
|
|
|
|
expectedReceivedMessages = publishMessagesForHandler(t, messagesCount, pub, sub, subscribeTopic1)
|
|
receivedMessages1, all := subscriber.BulkRead(receivedMessagesCh1, len(expectedReceivedMessages), time.Second*10)
|
|
assert.True(t, all)
|
|
tests.AssertAllMessagesReceived(t, expectedReceivedMessages, receivedMessages1)
|
|
|
|
handler.Stop()
|
|
select {
|
|
case <-handler.Stopped():
|
|
// ok
|
|
case <-time.After(time.Second):
|
|
t.Fatal("timeout waiting for handler stopped")
|
|
}
|
|
|
|
_ = publishMessagesForHandler(t, 1, pub, sub, subscribeTopic1)
|
|
_, received := subscriber.BulkRead(receivedMessagesCh1, 1, time.Millisecond*1)
|
|
assert.False(t, received)
|
|
|
|
receivedMessagesCh2 := make(chan *message.Message, messagesCount)
|
|
|
|
// we are adding the same handler again, with the same name
|
|
r.AddConsumerHandler(
|
|
"test_subscriber_1",
|
|
subscribeTopic2,
|
|
sub,
|
|
func(msg *message.Message) error {
|
|
receivedMessagesCh2 <- msg
|
|
return nil
|
|
},
|
|
)
|
|
err = r.RunHandlers(context.Background())
|
|
require.NoError(t, err)
|
|
|
|
expectedReceivedMessages = publishMessagesForHandler(t, messagesCount, pub, sub, subscribeTopic2)
|
|
receivedMessages2, all := subscriber.BulkRead(receivedMessagesCh2, len(expectedReceivedMessages), time.Second*10)
|
|
assert.True(t, all)
|
|
tests.AssertAllMessagesReceived(t, expectedReceivedMessages, receivedMessages2)
|
|
}
|
|
|
|
type subscriberMock struct {
|
|
messages chan *message.Message
|
|
}
|
|
|
|
func (s *subscriberMock) Subscribe(ctx context.Context, topic string) (<-chan *message.Message, error) {
|
|
return s.messages, nil
|
|
}
|
|
|
|
func (s *subscriberMock) Close() error {
|
|
close(s.messages)
|
|
return nil
|
|
}
|
|
|
|
type failingPublisherMock struct {
|
|
shouldPanic bool
|
|
shouldError bool
|
|
}
|
|
|
|
func (p *failingPublisherMock) Publish(topic string, messages ...*message.Message) error {
|
|
if p.shouldPanic {
|
|
panic("publisher panicked")
|
|
}
|
|
if p.shouldError {
|
|
return errors.New("publisher failed")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (p *failingPublisherMock) Close() error { return nil }
|
|
|
|
func TestRouter_stop_when_all_handlers_stopped(t *testing.T) {
|
|
pub1, sub1 := createPubSub()
|
|
pub2, sub2 := createPubSub()
|
|
|
|
defer func() {
|
|
assert.NoError(t, pub1.Close())
|
|
assert.NoError(t, sub1.Close())
|
|
assert.NoError(t, pub2.Close())
|
|
assert.NoError(t, sub2.Close())
|
|
}()
|
|
|
|
r, err := message.NewRouter(
|
|
message.RouterConfig{},
|
|
watermill.NewStdLogger(true, true),
|
|
)
|
|
require.NoError(t, err)
|
|
|
|
r.AddConsumerHandler(
|
|
"handler_1",
|
|
"foo",
|
|
sub1,
|
|
func(msg *message.Message) error {
|
|
return nil
|
|
},
|
|
)
|
|
|
|
r.AddConsumerHandler(
|
|
"handler_2",
|
|
"foo",
|
|
sub2,
|
|
func(msg *message.Message) error {
|
|
return nil
|
|
},
|
|
)
|
|
|
|
routerStopped := make(chan struct{})
|
|
go func() {
|
|
assert.NoError(t, r.Run(context.Background()))
|
|
close(routerStopped)
|
|
}()
|
|
<-r.Running()
|
|
|
|
require.NoError(t, pub1.Close())
|
|
require.NoError(t, sub1.Close())
|
|
select {
|
|
case <-routerStopped:
|
|
t.Fatal("only one handler has stopped")
|
|
case <-time.After(time.Millisecond * 100):
|
|
// ok
|
|
}
|
|
|
|
require.NoError(t, pub2.Close())
|
|
require.NoError(t, sub2.Close())
|
|
select {
|
|
case <-routerStopped:
|
|
// ok
|
|
case <-time.After(time.Second):
|
|
t.Fatal("router not stopped")
|
|
}
|
|
}
|
|
|
|
type benchMockSubscriber struct {
|
|
messagesToSend []*message.Message
|
|
}
|
|
|
|
func (m benchMockSubscriber) Subscribe(_ context.Context, topic string) (<-chan *message.Message, error) {
|
|
out := make(chan *message.Message)
|
|
|
|
go func() {
|
|
for _, msg := range m.messagesToSend {
|
|
out <- msg
|
|
<-msg.Acked()
|
|
}
|
|
|
|
close(out)
|
|
}()
|
|
|
|
return out, nil
|
|
}
|
|
|
|
func (benchMockSubscriber) Close() error {
|
|
return nil
|
|
}
|
|
|
|
type nopPublisher struct{}
|
|
|
|
func (nopPublisher) Publish(topic string, messages ...*message.Message) error { return nil }
|
|
func (nopPublisher) Close() error { return nil }
|
|
|
|
func BenchmarkRouterHandler(b *testing.B) {
|
|
logger := watermill.NopLogger{}
|
|
|
|
allProcessedWg := sync.WaitGroup{}
|
|
allProcessedWg.Add(b.N)
|
|
|
|
router, err := message.NewRouter(message.RouterConfig{}, logger)
|
|
if err != nil {
|
|
b.Fatal(err)
|
|
}
|
|
|
|
sub := createBenchSubscriber(b)
|
|
|
|
router.AddHandler(
|
|
"handler",
|
|
"benchmark_topic",
|
|
sub,
|
|
"publish_topic",
|
|
nopPublisher{},
|
|
func(msg *message.Message) (messages []*message.Message, e error) {
|
|
allProcessedWg.Done()
|
|
return []*message.Message{msg}, nil
|
|
},
|
|
)
|
|
|
|
go func() {
|
|
allProcessedWg.Wait()
|
|
_ = router.Close()
|
|
}()
|
|
|
|
b.ResetTimer()
|
|
if err := router.Run(context.Background()); err != nil {
|
|
b.Fatal(err)
|
|
}
|
|
}
|
|
|
|
func TestRouterNoPublisherHandler(t *testing.T) {
|
|
pub, sub := createPubSub()
|
|
defer func() {
|
|
assert.NoError(t, pub.Close())
|
|
assert.NoError(t, sub.Close())
|
|
}()
|
|
|
|
logger := watermill.NewCaptureLogger()
|
|
|
|
r, err := message.NewRouter(
|
|
message.RouterConfig{},
|
|
logger,
|
|
)
|
|
require.NoError(t, err)
|
|
|
|
wait := make(chan struct{})
|
|
|
|
r.AddConsumerHandler(
|
|
"test_no_publisher_handler",
|
|
"subscribe_topic",
|
|
sub,
|
|
func(msg *message.Message) error {
|
|
close(wait)
|
|
return nil
|
|
},
|
|
)
|
|
|
|
go func() {
|
|
err = r.Run(context.Background())
|
|
require.NoError(t, err)
|
|
}()
|
|
defer func() { _ = r.Close() }()
|
|
|
|
<-r.Running()
|
|
|
|
publishedMsg := message.NewMessage("1", nil)
|
|
err = pub.Publish("subscribe_topic", publishedMsg)
|
|
require.NoError(t, err)
|
|
|
|
select {
|
|
case <-wait:
|
|
// ok
|
|
case <-time.After(time.Second):
|
|
t.Fatal("no message received")
|
|
}
|
|
|
|
require.NoError(t, r.Close())
|
|
}
|
|
|
|
func BenchmarkRouterNoPublisherHandler(b *testing.B) {
|
|
logger := watermill.NopLogger{}
|
|
|
|
allProcessedWg := sync.WaitGroup{}
|
|
allProcessedWg.Add(b.N)
|
|
|
|
router, err := message.NewRouter(message.RouterConfig{}, logger)
|
|
if err != nil {
|
|
b.Fatal(err)
|
|
}
|
|
|
|
sub := createBenchSubscriber(b)
|
|
|
|
router.AddConsumerHandler(
|
|
"handler",
|
|
"benchmark_topic",
|
|
sub,
|
|
func(msg *message.Message) (e error) {
|
|
allProcessedWg.Done()
|
|
return nil
|
|
},
|
|
)
|
|
|
|
go func() {
|
|
allProcessedWg.Wait()
|
|
_ = router.Close()
|
|
}()
|
|
|
|
b.ResetTimer()
|
|
if err := router.Run(context.Background()); err != nil {
|
|
b.Fatal(err)
|
|
}
|
|
}
|
|
|
|
// TestRouterDecoratorsOrder checks that the publisher/subscriber decorators are applied in the order they are registered.
|
|
func TestRouterDecoratorsOrder(t *testing.T) {
|
|
logger := watermill.NewStdLogger(true, true)
|
|
|
|
router, err := message.NewRouter(message.RouterConfig{}, logger)
|
|
require.NoError(t, err)
|
|
|
|
pub, sub := createPubSub()
|
|
|
|
pubDecorator1 := message.MessageTransformPublisherDecorator(func(m *message.Message) {
|
|
m.Metadata.Set("pub", m.Metadata.Get("pub")+"foo")
|
|
})
|
|
pubDecorator2 := message.MessageTransformPublisherDecorator(func(m *message.Message) {
|
|
m.Metadata.Set("pub", m.Metadata.Get("pub")+"bar")
|
|
})
|
|
|
|
subDecorator1 := message.MessageTransformSubscriberDecorator(func(m *message.Message) {
|
|
m.Metadata.Set("sub", m.Metadata.Get("sub")+"foo")
|
|
})
|
|
subDecorator2 := message.MessageTransformSubscriberDecorator(func(m *message.Message) {
|
|
m.Metadata.Set("sub", m.Metadata.Get("sub")+"bar")
|
|
})
|
|
|
|
router.AddPublisherDecorators(pubDecorator1, pubDecorator2)
|
|
router.AddSubscriberDecorators(subDecorator1, subDecorator2)
|
|
|
|
router.AddHandler(
|
|
"handler",
|
|
"subTopic",
|
|
sub,
|
|
"pubTopic",
|
|
pub,
|
|
func(msg *message.Message) ([]*message.Message, error) {
|
|
return message.Messages{msg}, nil
|
|
},
|
|
)
|
|
|
|
go func() {
|
|
if err := router.Run(context.Background()); err != nil {
|
|
panic(err)
|
|
}
|
|
}()
|
|
defer func() {
|
|
if err := router.Close(); err != nil {
|
|
panic(err)
|
|
}
|
|
}()
|
|
<-router.Running()
|
|
|
|
transformedMessages, err := sub.Subscribe(context.Background(), "pubTopic")
|
|
require.NoError(t, err)
|
|
|
|
var transformedMessage *message.Message
|
|
messageObtained := make(chan struct{})
|
|
go func() {
|
|
transformedMessage = <-transformedMessages
|
|
close(messageObtained)
|
|
}()
|
|
|
|
require.NoError(t, pub.Publish("subTopic", message.NewMessage(watermill.NewUUID(), []byte{})))
|
|
|
|
select {
|
|
case <-time.After(5 * time.Second):
|
|
t.Fatal("test timed out")
|
|
case <-messageObtained:
|
|
}
|
|
|
|
assert.Equal(t, "foobar", transformedMessage.Metadata.Get("pub"))
|
|
assert.Equal(t, "foobar", transformedMessage.Metadata.Get("sub"))
|
|
}
|
|
|
|
func TestRouter_concurrent_close(t *testing.T) {
|
|
logger := watermill.NewStdLogger(true, true)
|
|
|
|
router, err := message.NewRouter(message.RouterConfig{}, logger)
|
|
require.NoError(t, err)
|
|
|
|
go func() {
|
|
err := router.Close()
|
|
require.NoError(t, err)
|
|
}()
|
|
|
|
err = router.Close()
|
|
require.NoError(t, err)
|
|
}
|
|
|
|
func TestRouter_concurrent_close_on_handlers_closed(t *testing.T) {
|
|
logger := watermill.NewStdLogger(true, true)
|
|
|
|
router, err := message.NewRouter(message.RouterConfig{}, logger)
|
|
require.NoError(t, err)
|
|
|
|
_, sub := createPubSub()
|
|
|
|
router.AddConsumerHandler(
|
|
"handler",
|
|
"subTopic",
|
|
sub,
|
|
func(msg *message.Message) error {
|
|
return nil
|
|
},
|
|
)
|
|
|
|
go func() {
|
|
if err := router.Run(context.Background()); err != nil {
|
|
panic(err)
|
|
}
|
|
}()
|
|
<-router.Running()
|
|
|
|
go func() {
|
|
err := sub.Close()
|
|
require.NoError(t, err)
|
|
}()
|
|
|
|
err = router.Close()
|
|
require.NoError(t, err)
|
|
}
|
|
|
|
func createBenchSubscriber(b *testing.B) benchMockSubscriber {
|
|
var messagesToSend []*message.Message
|
|
for i := 0; i < b.N; i++ {
|
|
messagesToSend = append(
|
|
messagesToSend,
|
|
message.NewMessage(watermill.NewUUID(), fmt.Appendf(nil, "%d", i)),
|
|
)
|
|
}
|
|
|
|
return benchMockSubscriber{messagesToSend}
|
|
}
|
|
|
|
func publishMessagesForHandler(t *testing.T, messagesCount int, pub message.Publisher, sub message.Subscriber, topicName string) []*message.Message {
|
|
var messagesToPublish []*message.Message
|
|
|
|
for i := 0; i < messagesCount; i++ {
|
|
msg := message.NewMessage(watermill.NewUUID(), fmt.Appendf(nil, "%d", i))
|
|
|
|
messagesToPublish = append(messagesToPublish, msg)
|
|
}
|
|
|
|
for _, msg := range messagesToPublish {
|
|
err := pub.Publish(topicName, msg)
|
|
require.NoError(t, err)
|
|
}
|
|
|
|
return messagesToPublish
|
|
}
|
|
|
|
func createPubSub() (message.Publisher, message.Subscriber) {
|
|
pubSub := gochannel.NewGoChannel(
|
|
gochannel.Config{Persistent: true},
|
|
watermill.NewStdLogger(true, true),
|
|
)
|
|
return pubSub, pubSub
|
|
}
|
|
|
|
func readMessages(messagesCh <-chan *message.Message, limit int, timeout time.Duration) (receivedMessages []*message.Message, all bool) {
|
|
allMessagesReceived := make(chan struct{}, 1)
|
|
|
|
go func() {
|
|
for msg := range messagesCh {
|
|
receivedMessages = append(receivedMessages, msg)
|
|
|
|
if len(receivedMessages) == limit {
|
|
allMessagesReceived <- struct{}{}
|
|
break
|
|
}
|
|
}
|
|
// messagesCh stopped
|
|
allMessagesReceived <- struct{}{}
|
|
}()
|
|
|
|
select {
|
|
case <-allMessagesReceived:
|
|
case <-time.After(timeout):
|
|
}
|
|
|
|
return receivedMessages, len(receivedMessages) == limit
|
|
}
|
|
|
|
func TestRouter_Handlers(t *testing.T) {
|
|
pub, sub := createPubSub()
|
|
defer func() {
|
|
assert.NoError(t, pub.Close())
|
|
assert.NoError(t, sub.Close())
|
|
}()
|
|
|
|
logger := watermill.NewCaptureLogger()
|
|
|
|
r, err := message.NewRouter(
|
|
message.RouterConfig{},
|
|
logger,
|
|
)
|
|
require.NoError(t, err)
|
|
|
|
handlerCalled := false
|
|
|
|
handlerName := "test_get_handler"
|
|
|
|
r.AddConsumerHandler(
|
|
handlerName,
|
|
"subscribe_topic",
|
|
sub,
|
|
func(msg *message.Message) error {
|
|
handlerCalled = true
|
|
return nil
|
|
},
|
|
)
|
|
|
|
actual := r.Handlers()
|
|
|
|
assert.Len(t, actual, 1)
|
|
|
|
actualHandler := actual[handlerName]
|
|
|
|
assert.NotNil(t, actualHandler)
|
|
|
|
messages, err := actualHandler(nil)
|
|
|
|
assert.Empty(t, messages)
|
|
assert.NoError(t, err)
|
|
assert.True(t, handlerCalled, "Handler function should be the same")
|
|
}
|
|
|
|
func TestRouter_wait_for_handlers_before_shutdown(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
pub, sub := createPubSub()
|
|
defer func() {
|
|
assert.NoError(t, pub.Close())
|
|
assert.NoError(t, sub.Close())
|
|
}()
|
|
|
|
logger := watermill.NewCaptureLogger()
|
|
|
|
r, err := message.NewRouter(
|
|
message.RouterConfig{},
|
|
logger,
|
|
)
|
|
require.NoError(t, err)
|
|
|
|
handlerStarted := make(chan struct{})
|
|
routerClosed := make(chan struct{})
|
|
|
|
r.AddConsumerHandler(
|
|
"foo",
|
|
"subscribe_topic",
|
|
sub,
|
|
func(msg *message.Message) error {
|
|
close(handlerStarted)
|
|
select {}
|
|
},
|
|
)
|
|
|
|
go func() {
|
|
err := r.Run(context.Background())
|
|
assert.NoError(t, err)
|
|
}()
|
|
<-r.Running()
|
|
|
|
err = pub.Publish("subscribe_topic", message.NewMessage(watermill.NewUUID(), nil))
|
|
require.NoError(t, err)
|
|
|
|
<-handlerStarted
|
|
|
|
go func() {
|
|
assert.NoError(t, r.Close())
|
|
close(routerClosed)
|
|
}()
|
|
|
|
select {
|
|
case <-routerClosed:
|
|
t.Fatal("Router should wait for handlers to finish")
|
|
case <-time.After(time.Millisecond * 100):
|
|
// ok, router is still running
|
|
}
|
|
}
|
|
|
|
func TestRouter_wait_for_handlers_before_shutdown_timeout(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
pub, sub := createPubSub()
|
|
defer func() {
|
|
assert.NoError(t, pub.Close())
|
|
assert.NoError(t, sub.Close())
|
|
}()
|
|
|
|
logger := watermill.NewCaptureLogger()
|
|
|
|
r, err := message.NewRouter(
|
|
message.RouterConfig{
|
|
CloseTimeout: time.Millisecond * 1,
|
|
},
|
|
logger,
|
|
)
|
|
require.NoError(t, err)
|
|
|
|
handlerStarted := make(chan struct{})
|
|
|
|
r.AddConsumerHandler(
|
|
"foo",
|
|
"subscribe_topic",
|
|
sub,
|
|
func(msg *message.Message) error {
|
|
close(handlerStarted)
|
|
select {}
|
|
},
|
|
)
|
|
|
|
go func() {
|
|
err := r.Run(context.Background())
|
|
assert.NoError(t, err)
|
|
}()
|
|
<-r.Running()
|
|
|
|
err = pub.Publish("subscribe_topic", message.NewMessage(watermill.NewUUID(), nil))
|
|
require.NoError(t, err)
|
|
|
|
<-handlerStarted
|
|
|
|
assert.EqualError(t, r.Close(), "router close timeout")
|
|
}
|
|
|
|
func TestRouter_context_cancel_does_not_log_error(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
pub, sub := createPubSub()
|
|
defer func() {
|
|
assert.NoError(t, pub.Close())
|
|
assert.NoError(t, sub.Close())
|
|
}()
|
|
|
|
logger := watermill.NewCaptureLogger()
|
|
|
|
r, err := message.NewRouter(message.RouterConfig{}, logger)
|
|
require.NoError(t, err)
|
|
|
|
r.AddConsumerHandler(
|
|
"foo",
|
|
"subscribe_topic",
|
|
sub,
|
|
func(msg *message.Message) error {
|
|
return nil
|
|
},
|
|
)
|
|
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
|
|
go func() {
|
|
err := r.Run(ctx)
|
|
assert.NoError(t, err)
|
|
}()
|
|
<-r.Running()
|
|
|
|
// Cancel the context
|
|
cancel()
|
|
|
|
require.Eventually(t, func() bool {
|
|
return r.IsClosed()
|
|
}, 3*time.Second, 5*time.Millisecond, "Router should be closed after all handlers are stopped")
|
|
|
|
assert.Empty(t, logger.Captured()[watermill.ErrorLogLevel], "No error should be logged when context is canceled")
|
|
}
|
|
|
|
// TestRouter_nack_on_context_canceled checks that the message is Nacked
|
|
// when the handler returns context.Canceled.
|
|
func TestRouter_nack_on_context_canceled(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
pubSub := gochannel.NewGoChannel(gochannel.Config{}, watermill.NopLogger{})
|
|
defer func() {
|
|
assert.NoError(t, pubSub.Close())
|
|
}()
|
|
|
|
logger := watermill.NewStdLogger(false, false) // Use StdLogger, logging check is in another test
|
|
|
|
r, err := message.NewRouter(message.RouterConfig{}, logger)
|
|
require.NoError(t, err)
|
|
|
|
handlerProcessed := make(chan struct{})
|
|
subscribeTopic := "test_nack_on_context_canceled_" + watermill.NewUUID()
|
|
|
|
r.AddConsumerHandler(
|
|
"test_handler",
|
|
subscribeTopic,
|
|
pubSub,
|
|
func(msg *message.Message) error {
|
|
defer func() {
|
|
handlerProcessed <- struct{}{}
|
|
}()
|
|
// Simulate handler returning context.Canceled
|
|
return context.Canceled
|
|
},
|
|
)
|
|
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
defer cancel()
|
|
|
|
runErrCh := make(chan error, 1)
|
|
go func() {
|
|
runErrCh <- r.Run(ctx)
|
|
}()
|
|
select {
|
|
case <-r.Running():
|
|
// proceed
|
|
case err := <-runErrCh:
|
|
t.Fatalf("Router failed to start: %v", err)
|
|
case <-time.After(5 * time.Second):
|
|
t.Fatal("Router did not start")
|
|
}
|
|
|
|
// Publish a message to trigger the handler
|
|
msg := message.NewMessage(watermill.NewUUID(), nil)
|
|
err = pubSub.Publish(subscribeTopic, msg)
|
|
require.NoError(t, err)
|
|
|
|
// Wait for the handler to process the message
|
|
select {
|
|
case <-handlerProcessed:
|
|
// ok
|
|
case <-time.After(5 * time.Second):
|
|
t.Fatal("Handler did not process message in time")
|
|
}
|
|
|
|
// Message should be re-sent when nacked
|
|
select {
|
|
case <-handlerProcessed:
|
|
// ok
|
|
case <-time.After(5 * time.Second):
|
|
t.Fatal("Handler did not process message in time")
|
|
}
|
|
}
|
|
|
|
func TestRouter_stopping_all_handlers_logs_error(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
pub, sub := createPubSub()
|
|
defer func() {
|
|
assert.NoError(t, pub.Close())
|
|
assert.NoError(t, sub.Close())
|
|
}()
|
|
|
|
logger := watermill.NewCaptureLogger()
|
|
|
|
defer logger.PrintCaptured(t)
|
|
|
|
r, err := message.NewRouter(message.RouterConfig{}, logger)
|
|
require.NoError(t, err)
|
|
|
|
r.AddConsumerHandler(
|
|
"foo",
|
|
"subscribe_topic",
|
|
sub,
|
|
func(msg *message.Message) error {
|
|
return nil
|
|
},
|
|
)
|
|
|
|
ctx := context.Background()
|
|
|
|
go func() {
|
|
err := r.Run(ctx)
|
|
assert.NoError(t, err)
|
|
}()
|
|
<-r.Running()
|
|
|
|
// Stop the subscriber - this should close the router with an error logged
|
|
err = sub.Close()
|
|
require.NoError(t, err)
|
|
|
|
require.Eventually(
|
|
t,
|
|
func() bool {
|
|
return r.IsClosed()
|
|
},
|
|
1*time.Second,
|
|
1*time.Millisecond,
|
|
"Router should be closed after all handlers are stopped",
|
|
)
|
|
|
|
expectedLogMessage := watermill.CapturedMessage{
|
|
Level: watermill.ErrorLogLevel,
|
|
Msg: "All handlers stopped, closing router",
|
|
Err: errors.New("all router handlers stopped"),
|
|
}
|
|
|
|
// Note: using logger.Has does not work here, since the error is not exposed (and thus not deep equal-able)
|
|
for _, capturedMessage := range logger.Captured()[watermill.ErrorLogLevel] {
|
|
if capturedMessage.Level == expectedLogMessage.Level &&
|
|
capturedMessage.Msg == expectedLogMessage.Msg &&
|
|
capturedMessage.Err.Error() == expectedLogMessage.Err.Error() {
|
|
return
|
|
}
|
|
}
|
|
|
|
assert.Fail(
|
|
t,
|
|
"expected log message not found, logs: %#v",
|
|
logger.Captured(),
|
|
)
|
|
}
|