mirror of
https://github.com/hanzo-fi/go-libs.git
synced 2026-10-30 20:53:44 +00:00
188 lines
5.0 KiB
Go
188 lines
5.0 KiB
Go
package queue_test
|
|
|
|
import (
|
|
"context"
|
|
"crypto/md5" //nolint: gosec
|
|
"encoding/hex"
|
|
"fmt"
|
|
"testing"
|
|
|
|
"github.com/ThreeDotsLabs/watermill-aws/sqs"
|
|
"github.com/ThreeDotsLabs/watermill/message"
|
|
"github.com/aws/aws-sdk-go-v2/aws"
|
|
"github.com/aws/aws-sdk-go-v2/credentials"
|
|
sqsservice "github.com/aws/aws-sdk-go-v2/service/sqs"
|
|
"github.com/aws/aws-sdk-go-v2/service/sqs/types"
|
|
"github.com/google/uuid"
|
|
. "github.com/onsi/ginkgo/v2"
|
|
. "github.com/onsi/gomega"
|
|
gomegatypes "github.com/onsi/gomega/types"
|
|
|
|
"github.com/hanzo-fi/go-libs/v5/pkg/messaging/queue"
|
|
logging "github.com/hanzo-fi/go-libs/v5/pkg/observe/log"
|
|
"github.com/hanzo-fi/go-libs/v5/pkg/testing/docker"
|
|
"github.com/hanzo-fi/go-libs/v5/pkg/testing/platform/localstacktesting"
|
|
"github.com/hanzo-fi/go-libs/v5/pkg/testing/utils"
|
|
)
|
|
|
|
var srv *localstacktesting.LocalstackServer
|
|
|
|
func TestMain(m *testing.M) {
|
|
utils.WithTestMain(func(t *utils.TestingTForMain) int {
|
|
pool := docker.NewPool(t, logging.Testing())
|
|
srv = localstacktesting.CreateLocalstackServer(t, pool,
|
|
localstacktesting.WithService("sqs"),
|
|
)
|
|
return m.Run()
|
|
})
|
|
}
|
|
|
|
func TestListener(t *testing.T) {
|
|
RegisterFailHandler(Fail)
|
|
RunSpecs(t, "Listener Suite")
|
|
}
|
|
|
|
func initClient() (client *sqsservice.Client, queueURL string, ch <-chan *message.Message, err error) {
|
|
ctx := logging.TestingContext()
|
|
|
|
cfg, err := srv.GetAWSConfig(ctx)
|
|
if err != nil {
|
|
return nil, "", ch, fmt.Errorf("Could not load config %w", err)
|
|
}
|
|
|
|
endpoint := srv.Endpoint()
|
|
optFns := []func(o *sqsservice.Options){
|
|
func(o *sqsservice.Options) {
|
|
o.BaseEndpoint = &endpoint
|
|
o.Credentials = credentials.NewStaticCredentialsProvider("dummy", "dummy", "dummy")
|
|
},
|
|
}
|
|
|
|
subscriber, err := sqs.NewSubscriber(sqs.SubscriberConfig{
|
|
AWSConfig: cfg,
|
|
OptFns: optFns,
|
|
}, nil)
|
|
if err != nil {
|
|
return nil, "", ch, fmt.Errorf("Failed to create subscriber %w", err)
|
|
}
|
|
|
|
client = sqsservice.NewFromConfig(cfg, optFns...)
|
|
queueName := uuid.NewString()
|
|
out, err := client.CreateQueue(ctx, &sqsservice.CreateQueueInput{
|
|
QueueName: &queueName,
|
|
})
|
|
if err != nil {
|
|
return nil, "", ch, fmt.Errorf("Could not create queue %w", err)
|
|
}
|
|
ch, err = subscriber.Subscribe(ctx, queueName)
|
|
if err != nil {
|
|
return nil, "", ch, fmt.Errorf("Failed to subscribe %w", err)
|
|
}
|
|
return client, *out.QueueUrl, ch, nil
|
|
}
|
|
|
|
var _ = Describe("SQS Listener", func() {
|
|
var (
|
|
client *sqsservice.Client
|
|
ch <-chan *message.Message
|
|
queueURL string
|
|
logger logging.Logger
|
|
err error
|
|
)
|
|
|
|
Context("listening", func() {
|
|
BeforeEach(func() {
|
|
client, queueURL, ch, err = initClient()
|
|
Expect(err).To(BeNil())
|
|
|
|
logger = logging.NewDefaultLogger(GinkgoWriter, true, true, false)
|
|
})
|
|
|
|
It("calls the callback function for each message", func(specCtx SpecContext) {
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
defer cancel()
|
|
|
|
metadataTagKey := "some-key"
|
|
expectedValue := "the-value"
|
|
var callbackCount int
|
|
var foundKey bool
|
|
var foundValue string
|
|
out := make(chan string)
|
|
fn := func(ctx context.Context, meta map[string]string, msg []byte) error {
|
|
callbackCount++
|
|
foundValue, foundKey = meta[metadataTagKey]
|
|
out <- string(msg)
|
|
return nil
|
|
}
|
|
listener, err := queue.NewListener(logger, fn, queue.WithWorkerCount(2))
|
|
Expect(err).To(BeNil())
|
|
DeferCleanup(listener.Done)
|
|
listener.Listen(ctx, ch)
|
|
|
|
body := "hi"
|
|
res, err := client.SendMessage(specCtx, &sqsservice.SendMessageInput{
|
|
QueueUrl: &queueURL,
|
|
MessageBody: &body,
|
|
MessageAttributes: map[string]types.MessageAttributeValue{
|
|
metadataTagKey: {
|
|
DataType: aws.String("String"),
|
|
StringValue: aws.String(expectedValue),
|
|
},
|
|
},
|
|
})
|
|
Expect(err).To(BeNil())
|
|
Eventually(out).Should(Receive(Message(*res.MD5OfMessageBody)))
|
|
Expect(callbackCount).To(Equal(1))
|
|
Expect(foundKey).To(BeTrue())
|
|
Expect(foundValue).To(Equal(expectedValue))
|
|
})
|
|
|
|
It("closes even if listen has never been called", func(specCtx SpecContext) {
|
|
fn := func(ctx context.Context, meta map[string]string, msg []byte) error {
|
|
return nil
|
|
}
|
|
listener, err := queue.NewListener(logger, fn, queue.WithWorkerCount(2))
|
|
Expect(err).To(BeNil())
|
|
DeferCleanup(listener.Done)
|
|
})
|
|
})
|
|
})
|
|
|
|
type MessageMatcher struct {
|
|
md5OfBody string
|
|
err error
|
|
}
|
|
|
|
func (m *MessageMatcher) Match(actual any) (success bool, err error) {
|
|
msg, ok := actual.(string)
|
|
if !ok {
|
|
m.err = fmt.Errorf("expected type string but got %T", actual)
|
|
return false, nil
|
|
}
|
|
|
|
//nolint: gosec
|
|
hash := md5.Sum([]byte(msg))
|
|
sum := hex.EncodeToString(hash[:])
|
|
if sum != m.md5OfBody {
|
|
m.err = fmt.Errorf("expected: %q, got: %q", m.md5OfBody, sum)
|
|
return false, nil
|
|
}
|
|
return true, nil
|
|
}
|
|
|
|
func (m *MessageMatcher) FailureMessage(_ any) (message string) {
|
|
return fmt.Sprintf("message does not match expectations: %s", m.err)
|
|
}
|
|
|
|
func (m *MessageMatcher) NegatedFailureMessage(_ any) (message string) {
|
|
return "message should not match"
|
|
}
|
|
|
|
var _ gomegatypes.GomegaMatcher = (*MessageMatcher)(nil)
|
|
|
|
func Message(md5OfBody string) gomegatypes.GomegaMatcher {
|
|
return &MessageMatcher{
|
|
md5OfBody: md5OfBody,
|
|
}
|
|
}
|