Files
nats.go/jetstream/pull_test.go
Piotr Piotrowski e7ab93ecb8 Add ordered consumer, FetchBytes and Next, rework options
Signed-off-by: Piotr Piotrowski <piotr@synadia.com>
2023-05-23 12:03:02 +02:00

1919 lines
50 KiB
Go

// Copyright 2020-2023 The NATS Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package jetstream
import (
"context"
"errors"
"fmt"
"sync"
"testing"
"time"
"github.com/nats-io/nats.go"
)
func TestPullConsumerFetch(t *testing.T) {
testSubject := "FOO.123"
testMsgs := []string{"m1", "m2", "m3", "m4", "m5"}
publishTestMsgs := func(t *testing.T, nc *nats.Conn) {
for _, msg := range testMsgs {
if err := nc.Publish(testSubject, []byte(msg)); err != nil {
t.Fatalf("Unexpected error during publish: %s", err)
}
}
}
t.Run("no options", func(t *testing.T) {
srv := RunBasicJetStreamServer()
defer shutdownJSServerAndRemoveStorage(t, srv)
nc, err := nats.Connect(srv.ClientURL())
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
js, err := New(nc)
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
defer nc.Close()
s, err := js.CreateStream(ctx, StreamConfig{Name: "foo", Subjects: []string{"FOO.*"}})
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
c, err := s.AddConsumer(ctx, ConsumerConfig{AckPolicy: AckExplicitPolicy})
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
publishTestMsgs(t, nc)
msgs, err := c.Fetch(5)
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
var i int
for msg := range msgs.Messages() {
if string(msg.Data()) != testMsgs[i] {
t.Fatalf("Invalid msg on index %d; expected: %s; got: %s", i, testMsgs[i], string(msg.Data()))
}
i++
}
if len(testMsgs) != i {
t.Fatalf("Invalid number of messages received; want: %d; got: %d", len(testMsgs), i)
}
if msgs.Error() != nil {
t.Fatalf("Unexpected error during fetch: %v", msgs.Error())
}
})
t.Run("no options, fetch single messages one by one", func(t *testing.T) {
srv := RunBasicJetStreamServer()
defer shutdownJSServerAndRemoveStorage(t, srv)
nc, err := nats.Connect(srv.ClientURL())
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
js, err := New(nc)
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
defer nc.Close()
s, err := js.CreateStream(ctx, StreamConfig{Name: "foo", Subjects: []string{"FOO.*"}})
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
c, err := s.AddConsumer(ctx, ConsumerConfig{AckPolicy: AckExplicitPolicy})
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
res := make([]Msg, 0)
errs := make(chan error)
done := make(chan struct{})
go func() {
for {
if len(res) == len(testMsgs) {
close(done)
return
}
msgs, err := c.Fetch(1)
if err != nil {
errs <- err
return
}
msg := <-msgs.Messages()
if msg != nil {
res = append(res, msg)
}
if err := msgs.Error(); err != nil {
errs <- err
return
}
}
}()
time.Sleep(10 * time.Millisecond)
publishTestMsgs(t, nc)
select {
case err := <-errs:
t.Fatalf("Unexpected error: %v", err)
case <-done:
if len(res) != len(testMsgs) {
t.Fatalf("Unexpected received message count; want %d; got %d", len(testMsgs), len(res))
}
}
for i, msg := range res {
if string(msg.Data()) != testMsgs[i] {
t.Fatalf("Invalid msg on index %d; expected: %s; got: %s", i, testMsgs[i], string(msg.Data()))
}
}
})
t.Run("with no wait, no messages at the time of request", func(t *testing.T) {
srv := RunBasicJetStreamServer()
defer shutdownJSServerAndRemoveStorage(t, srv)
nc, err := nats.Connect(srv.ClientURL())
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
js, err := New(nc)
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
defer nc.Close()
s, err := js.CreateStream(ctx, StreamConfig{Name: "foo", Subjects: []string{"FOO.*"}})
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
c, err := s.AddConsumer(ctx, ConsumerConfig{AckPolicy: AckExplicitPolicy})
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
msgs, err := c.FetchNoWait(5)
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
publishTestMsgs(t, nc)
msg := <-msgs.Messages()
if msg != nil {
t.Fatalf("Expected no messages; got: %s", string(msg.Data()))
}
})
t.Run("with no wait, some messages available", func(t *testing.T) {
srv := RunBasicJetStreamServer()
defer shutdownJSServerAndRemoveStorage(t, srv)
nc, err := nats.Connect(srv.ClientURL())
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
js, err := New(nc)
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
defer nc.Close()
s, err := js.CreateStream(ctx, StreamConfig{Name: "foo", Subjects: []string{"FOO.*"}})
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
c, err := s.AddConsumer(ctx, ConsumerConfig{AckPolicy: AckExplicitPolicy})
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
publishTestMsgs(t, nc)
time.Sleep(50 * time.Millisecond)
msgs, err := c.FetchNoWait(10)
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
time.Sleep(100 * time.Millisecond)
publishTestMsgs(t, nc)
var msgsNum int
for range msgs.Messages() {
msgsNum++
}
if err != nil {
t.Fatalf("Unexpected error during fetch: %v", err)
}
if msgsNum != len(testMsgs) {
t.Fatalf("Expected 5 messages, got: %d", msgsNum)
}
})
t.Run("with timeout", func(t *testing.T) {
srv := RunBasicJetStreamServer()
defer shutdownJSServerAndRemoveStorage(t, srv)
nc, err := nats.Connect(srv.ClientURL())
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
js, err := New(nc)
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
defer nc.Close()
s, err := js.CreateStream(ctx, StreamConfig{Name: "foo", Subjects: []string{"FOO.*"}})
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
c, err := s.AddConsumer(ctx, ConsumerConfig{AckPolicy: AckExplicitPolicy})
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
msgs, err := c.Fetch(5, FetchMaxWait(50*time.Millisecond))
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
msg := <-msgs.Messages()
if msg != nil {
t.Fatalf("Expected no messages; got: %s", string(msg.Data()))
}
})
t.Run("with invalid timeout value", func(t *testing.T) {
srv := RunBasicJetStreamServer()
defer shutdownJSServerAndRemoveStorage(t, srv)
nc, err := nats.Connect(srv.ClientURL())
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
js, err := New(nc)
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
defer nc.Close()
s, err := js.CreateStream(ctx, StreamConfig{Name: "foo", Subjects: []string{"FOO.*"}})
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
c, err := s.AddConsumer(ctx, ConsumerConfig{AckPolicy: AckExplicitPolicy})
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
_, err = c.Fetch(5, FetchMaxWait(-50*time.Millisecond))
if !errors.Is(err, ErrInvalidOption) {
t.Fatalf("Expected error: %v; got: %v", ErrInvalidOption, err)
}
})
}
func TestPullConsumerFetchBytes(t *testing.T) {
testSubject := "FOO.123"
msg := [10]byte{}
publishTestMsgs := func(t *testing.T, nc *nats.Conn, count int) {
for i := 0; i < count; i++ {
if err := nc.Publish(testSubject, msg[:]); err != nil {
t.Fatalf("Unexpected error during publish: %s", err)
}
}
}
t.Run("no options, exact byte count received", func(t *testing.T) {
srv := RunBasicJetStreamServer()
defer shutdownJSServerAndRemoveStorage(t, srv)
nc, err := nats.Connect(srv.ClientURL())
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
js, err := New(nc)
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
defer nc.Close()
s, err := js.CreateStream(ctx, StreamConfig{Name: "foo", Subjects: []string{"FOO.*"}})
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
c, err := s.AddConsumer(ctx, ConsumerConfig{AckPolicy: AckExplicitPolicy, Name: "con"})
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
publishTestMsgs(t, nc, 5)
// actual received msg size will be 60 (payload=10 + Subject=7 + Reply=43)
msgs, err := c.FetchBytes(300)
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
var i int
for msg := range msgs.Messages() {
msg.Ack()
i++
}
if i != 5 {
t.Fatalf("Expected 5 messages; got: %d", i)
}
if msgs.Error() != nil {
t.Fatalf("Unexpected error during fetch: %v", msgs.Error())
}
})
t.Run("no options, last msg does not fit max bytes", func(t *testing.T) {
srv := RunBasicJetStreamServer()
defer shutdownJSServerAndRemoveStorage(t, srv)
nc, err := nats.Connect(srv.ClientURL())
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
js, err := New(nc)
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
defer nc.Close()
s, err := js.CreateStream(ctx, StreamConfig{Name: "foo", Subjects: []string{"FOO.*"}})
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
c, err := s.AddConsumer(ctx, ConsumerConfig{AckPolicy: AckExplicitPolicy, Name: "con"})
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
publishTestMsgs(t, nc, 5)
// actual received msg size will be 60 (payload=10 + Subject=7 + Reply=43)
msgs, err := c.FetchBytes(250)
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
var i int
for msg := range msgs.Messages() {
msg.Ack()
i++
}
if i != 4 {
t.Fatalf("Expected 5 messages; got: %d", i)
}
if msgs.Error() != nil {
t.Fatalf("Unexpected error during fetch: %v", msgs.Error())
}
})
t.Run("no options, single msg is too large", func(t *testing.T) {
srv := RunBasicJetStreamServer()
defer shutdownJSServerAndRemoveStorage(t, srv)
nc, err := nats.Connect(srv.ClientURL())
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
js, err := New(nc)
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
defer nc.Close()
s, err := js.CreateStream(ctx, StreamConfig{Name: "foo", Subjects: []string{"FOO.*"}})
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
c, err := s.AddConsumer(ctx, ConsumerConfig{AckPolicy: AckExplicitPolicy, Name: "con"})
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
publishTestMsgs(t, nc, 5)
// actual received msg size will be 60 (payload=10 + Subject=7 + Reply=43)
msgs, err := c.FetchBytes(30)
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
var i int
for msg := range msgs.Messages() {
msg.Ack()
i++
}
if i != 0 {
t.Fatalf("Expected 5 messages; got: %d", i)
}
if msgs.Error() != nil {
t.Fatalf("Unexpected error during fetch: %v", msgs.Error())
}
})
t.Run("timeout waiting for messages", func(t *testing.T) {
srv := RunBasicJetStreamServer()
defer shutdownJSServerAndRemoveStorage(t, srv)
nc, err := nats.Connect(srv.ClientURL())
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
js, err := New(nc)
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
defer nc.Close()
s, err := js.CreateStream(ctx, StreamConfig{Name: "foo", Subjects: []string{"FOO.*"}})
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
c, err := s.AddConsumer(ctx, ConsumerConfig{AckPolicy: AckExplicitPolicy, Name: "con"})
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
publishTestMsgs(t, nc, 5)
// actual received msg size will be 60 (payload=10 + Subject=7 + Reply=43)
msgs, err := c.FetchBytes(1000, FetchMaxWait(50*time.Millisecond))
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
var i int
for msg := range msgs.Messages() {
msg.Ack()
i++
}
if i != 5 {
t.Fatalf("Expected 5 messages; got: %d", i)
}
if msgs.Error() != nil {
t.Fatalf("Unexpected error during fetch: %v", msgs.Error())
}
})
}
func TestPullConsumerFetch_WithCluster(t *testing.T) {
testSubject := "FOO.123"
testMsgs := []string{"m1", "m2", "m3", "m4", "m5"}
publishTestMsgs := func(t *testing.T, nc *nats.Conn) {
for _, msg := range testMsgs {
if err := nc.Publish(testSubject, []byte(msg)); err != nil {
t.Fatalf("Unexpected error during publish: %s", err)
}
}
}
name := "cluster"
stream := StreamConfig{
Name: name,
Replicas: 1,
Subjects: []string{"FOO.*"},
}
t.Run("no options", func(t *testing.T) {
withJSClusterAndStream(t, name, 3, stream, func(t *testing.T, subject string, srvs ...*jsServer) {
srv := srvs[0]
nc, err := nats.Connect(srv.ClientURL())
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
js, err := New(nc)
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
defer nc.Close()
s, err := js.Stream(ctx, stream.Name)
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
c, err := s.AddConsumer(ctx, ConsumerConfig{AckPolicy: AckExplicitPolicy})
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
publishTestMsgs(t, nc)
msgs, err := c.Fetch(5)
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
var i int
for msg := range msgs.Messages() {
if string(msg.Data()) != testMsgs[i] {
t.Fatalf("Invalid msg on index %d; expected: %s; got: %s", i, testMsgs[i], string(msg.Data()))
}
i++
}
if msgs.Error() != nil {
t.Fatalf("Unexpected error during fetch: %v", msgs.Error())
}
})
})
t.Run("with no wait, no messages at the time of request", func(t *testing.T) {
withJSClusterAndStream(t, name, 3, stream, func(t *testing.T, subject string, srvs ...*jsServer) {
nc, err := nats.Connect(srvs[0].ClientURL())
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
js, err := New(nc)
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
defer nc.Close()
s, err := js.Stream(ctx, stream.Name)
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
c, err := s.AddConsumer(ctx, ConsumerConfig{AckPolicy: AckExplicitPolicy})
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
msgs, err := c.FetchNoWait(5)
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
publishTestMsgs(t, nc)
msg := <-msgs.Messages()
if msg != nil {
t.Fatalf("Expected no messages; got: %s", string(msg.Data()))
}
})
})
}
func TestPullConsumerMessages(t *testing.T) {
testSubject := "FOO.123"
testMsgs := []string{"m1", "m2", "m3", "m4", "m5"}
publishTestMsgs := func(t *testing.T, nc *nats.Conn) {
for _, msg := range testMsgs {
if err := nc.Publish(testSubject, []byte(msg)); err != nil {
t.Fatalf("Unexpected error during publish: %s", err)
}
}
}
t.Run("no options", func(t *testing.T) {
srv := RunBasicJetStreamServer()
defer shutdownJSServerAndRemoveStorage(t, srv)
nc, err := nats.Connect(srv.ClientURL())
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
js, err := New(nc)
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
defer nc.Close()
ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second)
defer cancel()
s, err := js.CreateStream(ctx, StreamConfig{Name: "foo", Subjects: []string{"FOO.*"}})
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
c, err := s.AddConsumer(ctx, ConsumerConfig{AckPolicy: AckExplicitPolicy})
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
msgs := make([]Msg, 0)
it, err := c.Messages()
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
publishTestMsgs(t, nc)
for i := 0; i < len(testMsgs); i++ {
msg, err := it.Next()
if err != nil {
t.Fatal(err)
}
if msg == nil {
break
}
msg.Ack()
msgs = append(msgs, msg)
}
it.Stop()
// calling Stop() multiple times should have no effect
it.Stop()
if len(msgs) != len(testMsgs) {
t.Fatalf("Unexpected received message count; want %d; got %d", len(testMsgs), len(msgs))
}
for i, msg := range msgs {
if string(msg.Data()) != testMsgs[i] {
t.Fatalf("Invalid msg on index %d; expected: %s; got: %s", i, testMsgs[i], string(msg.Data()))
}
}
_, err = it.Next()
if err == nil || !errors.Is(err, ErrMsgIteratorClosed) {
t.Fatalf("Expected error: %v; got: %v", ErrMsgIteratorClosed, err)
}
})
t.Run("with custom batch size", func(t *testing.T) {
srv := RunBasicJetStreamServer()
defer shutdownJSServerAndRemoveStorage(t, srv)
nc, err := nats.Connect(srv.ClientURL())
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
js, err := New(nc)
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
defer nc.Close()
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
s, err := js.CreateStream(ctx, StreamConfig{Name: "foo", Subjects: []string{"FOO.*"}})
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
c, err := s.AddConsumer(ctx, ConsumerConfig{AckPolicy: AckExplicitPolicy})
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
msgs := make([]Msg, 0)
it, err := c.Messages(PullMaxMessages(3))
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
publishTestMsgs(t, nc)
for i := 0; i < len(testMsgs); i++ {
msg, err := it.Next()
if err != nil {
t.Fatal(err)
}
if msg == nil {
break
}
msg.Ack()
msgs = append(msgs, msg)
}
it.Stop()
time.Sleep(10 * time.Millisecond)
if len(msgs) != len(testMsgs) {
t.Fatalf("Unexpected received message count; want %d; got %d", len(testMsgs), len(msgs))
}
for i, msg := range msgs {
if string(msg.Data()) != testMsgs[i] {
t.Fatalf("Invalid msg on index %d; expected: %s; got: %s", i, testMsgs[i], string(msg.Data()))
}
}
})
t.Run("with max fitting 1 message", func(t *testing.T) {
srv := RunBasicJetStreamServer()
defer shutdownJSServerAndRemoveStorage(t, srv)
nc, err := nats.Connect(srv.ClientURL())
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
js, err := New(nc)
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
defer nc.Close()
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
s, err := js.CreateStream(ctx, StreamConfig{Name: "foo", Subjects: []string{"FOO.*"}})
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
c, err := s.AddConsumer(ctx, ConsumerConfig{AckPolicy: AckExplicitPolicy})
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
// subscribe to next request subject to verify how many next requests were sent
sub, err := nc.SubscribeSync(fmt.Sprintf("$JS.API.CONSUMER.MSG.NEXT.foo.%s", c.CachedInfo().Name))
if err != nil {
t.Fatalf("Error on subscribe: %v", err)
}
msgs := make([]Msg, 0)
it, err := c.Messages(PullMaxBytes(60))
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
publishTestMsgs(t, nc)
for i := 0; i < len(testMsgs); i++ {
msg, err := it.Next()
if err != nil {
t.Fatal(err)
}
if msg == nil {
break
}
msg.Ack()
msgs = append(msgs, msg)
}
it.Stop()
time.Sleep(10 * time.Millisecond)
requestsNum, _, err := sub.Pending()
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
// with batch size set to 1, and 5 messages published on subject, there should be a total of 5 requests sent
if requestsNum < 5 {
t.Fatalf("Unexpected number of requests sent; want at least 5; got %d", requestsNum)
}
if len(msgs) != len(testMsgs) {
t.Fatalf("Unexpected received message count; want %d; got %d", len(testMsgs), len(msgs))
}
for i, msg := range msgs {
if string(msg.Data()) != testMsgs[i] {
t.Fatalf("Invalid msg on index %d; expected: %s; got: %s", i, testMsgs[i], string(msg.Data()))
}
}
})
t.Run("with custom max bytes", func(t *testing.T) {
srv := RunBasicJetStreamServer()
defer shutdownJSServerAndRemoveStorage(t, srv)
nc, err := nats.Connect(srv.ClientURL())
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
js, err := New(nc)
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
defer nc.Close()
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
s, err := js.CreateStream(ctx, StreamConfig{Name: "foo", Subjects: []string{"FOO.*"}})
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
c, err := s.AddConsumer(ctx, ConsumerConfig{AckPolicy: AckExplicitPolicy})
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
// subscribe to next request subject to verify how many next requests were sent
sub, err := nc.SubscribeSync(fmt.Sprintf("$JS.API.CONSUMER.MSG.NEXT.foo.%s", c.CachedInfo().Name))
if err != nil {
t.Fatalf("Error on subscribe: %v", err)
}
msgs := make([]Msg, 0)
it, err := c.Messages(PullMaxBytes(150))
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
publishTestMsgs(t, nc)
for i := 0; i < len(testMsgs); i++ {
msg, err := it.Next()
if err != nil {
t.Fatal(err)
}
if msg == nil {
break
}
msg.Ack()
msgs = append(msgs, msg)
}
it.Stop()
time.Sleep(10 * time.Millisecond)
requestsNum, _, err := sub.Pending()
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
if requestsNum < 3 {
t.Fatalf("Unexpected number of requests sent; want at least 3; got %d", requestsNum)
}
if len(msgs) != len(testMsgs) {
t.Fatalf("Unexpected received message count; want %d; got %d", len(testMsgs), len(msgs))
}
for i, msg := range msgs {
if string(msg.Data()) != testMsgs[i] {
t.Fatalf("Invalid msg on index %d; expected: %s; got: %s", i, testMsgs[i], string(msg.Data()))
}
}
})
t.Run("with batch size set to 1", func(t *testing.T) {
srv := RunBasicJetStreamServer()
defer shutdownJSServerAndRemoveStorage(t, srv)
nc, err := nats.Connect(srv.ClientURL())
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
js, err := New(nc)
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
defer nc.Close()
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
s, err := js.CreateStream(ctx, StreamConfig{Name: "foo", Subjects: []string{"FOO.*"}})
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
c, err := s.AddConsumer(ctx, ConsumerConfig{AckPolicy: AckExplicitPolicy})
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
// subscribe to next request subject to verify how many next requests were sent
sub, err := nc.SubscribeSync(fmt.Sprintf("$JS.API.CONSUMER.MSG.NEXT.foo.%s", c.CachedInfo().Name))
if err != nil {
t.Fatalf("Error on subscribe: %v", err)
}
msgs := make([]Msg, 0)
it, err := c.Messages(PullMaxMessages(1))
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
publishTestMsgs(t, nc)
for i := 0; i < len(testMsgs); i++ {
msg, err := it.Next()
if err != nil {
t.Fatal(err)
}
if msg == nil {
break
}
msg.Ack()
msgs = append(msgs, msg)
}
it.Stop()
time.Sleep(10 * time.Millisecond)
requestsNum, _, err := sub.Pending()
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
// with batch size set to 1, and 5 messages published on subject, there should be a total of 5 requests sent
if requestsNum != 5 {
t.Fatalf("Unexpected number of requests sent; want 5; got %d", requestsNum)
}
if len(msgs) != len(testMsgs) {
t.Fatalf("Unexpected received message count; want %d; got %d", len(testMsgs), len(msgs))
}
for i, msg := range msgs {
if string(msg.Data()) != testMsgs[i] {
t.Fatalf("Invalid msg on index %d; expected: %s; got: %s", i, testMsgs[i], string(msg.Data()))
}
}
})
t.Run("create iterator, stop, then create again", func(t *testing.T) {
srv := RunBasicJetStreamServer()
defer shutdownJSServerAndRemoveStorage(t, srv)
nc, err := nats.Connect(srv.ClientURL())
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
js, err := New(nc)
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
defer nc.Close()
ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second)
defer cancel()
s, err := js.CreateStream(ctx, StreamConfig{Name: "foo", Subjects: []string{"FOO.*"}})
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
c, err := s.AddConsumer(ctx, ConsumerConfig{AckPolicy: AckExplicitPolicy})
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
msgs := make([]Msg, 0)
it, err := c.Messages()
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
publishTestMsgs(t, nc)
for i := 0; i < len(testMsgs); i++ {
msg, err := it.Next()
if err != nil {
t.Fatal(err)
}
if msg == nil {
break
}
msg.Ack()
msgs = append(msgs, msg)
}
it.Stop()
time.Sleep(10 * time.Millisecond)
publishTestMsgs(t, nc)
it, err = c.Messages()
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
for i := 0; i < len(testMsgs); i++ {
msg, err := it.Next()
if err != nil {
t.Fatal(err)
}
if msg == nil {
break
}
msg.Ack()
msgs = append(msgs, msg)
}
it.Stop()
if len(msgs) != 2*len(testMsgs) {
t.Fatalf("Unexpected received message count; want %d; got %d", len(testMsgs), len(msgs))
}
expectedMsgs := append(testMsgs, testMsgs...)
for i, msg := range msgs {
if string(msg.Data()) != expectedMsgs[i] {
t.Fatalf("Invalid msg on index %d; expected: %s; got: %s", i, testMsgs[i], string(msg.Data()))
}
}
})
t.Run("with invalid batch size", func(t *testing.T) {
srv := RunBasicJetStreamServer()
defer shutdownJSServerAndRemoveStorage(t, srv)
nc, err := nats.Connect(srv.ClientURL())
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
js, err := New(nc)
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
defer nc.Close()
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
s, err := js.CreateStream(ctx, StreamConfig{Name: "foo", Subjects: []string{"FOO.*"}})
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
c, err := s.AddConsumer(ctx, ConsumerConfig{AckPolicy: AckExplicitPolicy})
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
_, err = c.Messages(PullMaxMessages(-1))
if err == nil || !errors.Is(err, ErrInvalidOption) {
t.Fatalf("Expected error: %v; got: %v", ErrInvalidOption, err)
}
})
t.Run("with idle heartbeat", func(t *testing.T) {
srv := RunBasicJetStreamServer()
defer shutdownJSServerAndRemoveStorage(t, srv)
nc, err := nats.Connect(srv.ClientURL())
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
js, err := New(nc)
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
defer nc.Close()
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
s, err := js.CreateStream(ctx, StreamConfig{Name: "foo", Subjects: []string{"FOO.*"}})
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
c, err := s.AddConsumer(ctx, ConsumerConfig{AckPolicy: AckExplicitPolicy})
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
msgs := make([]Msg, 0)
// use custom function to bypass validation in test
it, err := c.Messages(pullOptFunc(func(o *consumeOpts) error {
o.Heartbeat = 10 * time.Millisecond
return nil
}))
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
publishTestMsgs(t, nc)
for i := 0; i < len(testMsgs); i++ {
msg, err := it.Next()
if err != nil {
t.Fatal(err)
}
if msg == nil {
break
}
msg.Ack()
msgs = append(msgs, msg)
}
it.Stop()
if len(msgs) != len(testMsgs) {
t.Fatalf("Unexpected received message count; want %d; got %d", len(testMsgs), len(msgs))
}
for i, msg := range msgs {
if string(msg.Data()) != testMsgs[i] {
t.Fatalf("Invalid msg on index %d; expected: %s; got: %s", i, testMsgs[i], string(msg.Data()))
}
}
})
t.Run("with server restart", func(t *testing.T) {
srv := RunBasicJetStreamServer()
nc, err := nats.Connect(srv.ClientURL())
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
js, err := New(nc)
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
defer nc.Close()
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
s, err := js.CreateStream(ctx, StreamConfig{Name: "foo", Subjects: []string{"FOO.*"}})
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
c, err := s.AddConsumer(ctx, ConsumerConfig{AckPolicy: AckExplicitPolicy})
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
msgs := make([]Msg, 0)
it, err := c.Messages()
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
done := make(chan struct{})
errs := make(chan error)
publishTestMsgs(t, nc)
go func() {
for i := 0; i < 2*len(testMsgs); i++ {
msg, err := it.Next()
if err != nil {
errs <- err
return
}
msg.Ack()
msgs = append(msgs, msg)
}
done <- struct{}{}
}()
time.Sleep(10 * time.Millisecond)
// restart the server
srv = restartBasicJSServer(t, srv)
defer shutdownJSServerAndRemoveStorage(t, srv)
time.Sleep(10 * time.Millisecond)
publishTestMsgs(t, nc)
select {
case <-done:
if len(msgs) != 2*len(testMsgs) {
t.Fatalf("Unexpected received message count; want %d; got %d", len(testMsgs), len(msgs))
}
case err := <-errs:
t.Fatalf("Unexpected error: %s", err)
}
})
}
func TestPullConsumerConsume(t *testing.T) {
testSubject := "FOO.123"
testMsgs := []string{"m1", "m2", "m3", "m4", "m5"}
publishTestMsgs := func(t *testing.T, nc *nats.Conn) {
for _, msg := range testMsgs {
if err := nc.Publish(testSubject, []byte(msg)); err != nil {
t.Fatalf("Unexpected error during publish: %s", err)
}
}
}
t.Run("no options", func(t *testing.T) {
srv := RunBasicJetStreamServer()
defer shutdownJSServerAndRemoveStorage(t, srv)
nc, err := nats.Connect(srv.ClientURL())
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
js, err := New(nc)
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
defer nc.Close()
ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second)
defer cancel()
s, err := js.CreateStream(ctx, StreamConfig{Name: "foo", Subjects: []string{"FOO.*"}})
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
c, err := s.AddConsumer(ctx, ConsumerConfig{AckPolicy: AckExplicitPolicy})
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
msgs := make([]Msg, 0)
wg := &sync.WaitGroup{}
wg.Add(len(testMsgs))
l, err := c.Consume(func(msg Msg) {
msgs = append(msgs, msg)
wg.Done()
})
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
defer l.Stop()
publishTestMsgs(t, nc)
wg.Wait()
if len(msgs) != len(testMsgs) {
t.Fatalf("Unexpected received message count; want %d; got %d", len(testMsgs), len(msgs))
}
for i, msg := range msgs {
if string(msg.Data()) != testMsgs[i] {
t.Fatalf("Invalid msg on index %d; expected: %s; got: %s", i, testMsgs[i], string(msg.Data()))
}
}
})
t.Run("subscribe twice on the same consumer", func(t *testing.T) {
srv := RunBasicJetStreamServer()
defer shutdownJSServerAndRemoveStorage(t, srv)
nc, err := nats.Connect(srv.ClientURL())
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
js, err := New(nc)
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
defer nc.Close()
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
s, err := js.CreateStream(ctx, StreamConfig{Name: "foo", Subjects: []string{"FOO.*"}})
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
c, err := s.AddConsumer(ctx, ConsumerConfig{AckPolicy: AckExplicitPolicy})
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
wg := sync.WaitGroup{}
msgs1, msgs2 := make([]Msg, 0), make([]Msg, 0)
l1, err := c.Consume(func(msg Msg) {
msgs1 = append(msgs1, msg)
wg.Done()
msg.Ack()
})
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
defer l1.Stop()
l2, err := c.Consume(func(msg Msg) {
msgs2 = append(msgs2, msg)
wg.Done()
msg.Ack()
})
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
defer l2.Stop()
wg.Add(len(testMsgs))
publishTestMsgs(t, nc)
wg.Wait()
if len(msgs1)+len(msgs2) != len(testMsgs) {
t.Fatalf("Unexpected received message count; want %d; got %d", len(testMsgs), len(msgs1)+len(msgs2))
}
if len(msgs1) == 0 || len(msgs2) == 0 {
t.Fatalf("Received no messages on one of the subscriptions")
}
})
t.Run("subscribe, cancel subscription, then subscribe again", func(t *testing.T) {
srv := RunBasicJetStreamServer()
defer shutdownJSServerAndRemoveStorage(t, srv)
nc, err := nats.Connect(srv.ClientURL())
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
js, err := New(nc)
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
defer nc.Close()
ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second)
defer cancel()
s, err := js.CreateStream(ctx, StreamConfig{Name: "foo", Subjects: []string{"FOO.*"}})
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
c, err := s.AddConsumer(ctx, ConsumerConfig{AckPolicy: AckExplicitPolicy})
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
wg := sync.WaitGroup{}
wg.Add(len(testMsgs))
msgs := make([]Msg, 0)
l, err := c.Consume(func(msg Msg) {
if err := msg.Ack(); err != nil {
t.Fatalf("Unexpected error: %v", err)
}
msgs = append(msgs, msg)
wg.Done()
})
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
publishTestMsgs(t, nc)
wg.Wait()
l.Stop()
time.Sleep(10 * time.Millisecond)
wg.Add(len(testMsgs))
l, err = c.Consume(func(msg Msg) {
if err := msg.Ack(); err != nil {
t.Fatalf("Unexpected error: %v", err)
}
msgs = append(msgs, msg)
wg.Done()
})
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
defer l.Stop()
publishTestMsgs(t, nc)
wg.Wait()
if len(msgs) != 2*len(testMsgs) {
t.Fatalf("Unexpected received message count; want %d; got %d", len(testMsgs), len(msgs))
}
expectedMsgs := append(testMsgs, testMsgs...)
for i, msg := range msgs {
if string(msg.Data()) != expectedMsgs[i] {
t.Fatalf("Invalid msg on index %d; expected: %s; got: %s", i, testMsgs[i], string(msg.Data()))
}
}
})
t.Run("with custom batch size", func(t *testing.T) {
srv := RunBasicJetStreamServer()
defer shutdownJSServerAndRemoveStorage(t, srv)
nc, err := nats.Connect(srv.ClientURL())
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
js, err := New(nc)
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
defer nc.Close()
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
s, err := js.CreateStream(ctx, StreamConfig{Name: "foo", Subjects: []string{"FOO.*"}})
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
c, err := s.AddConsumer(ctx, ConsumerConfig{AckPolicy: AckExplicitPolicy})
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
msgs := make([]Msg, 0)
wg := &sync.WaitGroup{}
wg.Add(len(testMsgs))
l, err := c.Consume(func(msg Msg) {
msgs = append(msgs, msg)
wg.Done()
}, PullMaxMessages(4))
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
defer l.Stop()
publishTestMsgs(t, nc)
wg.Wait()
if len(msgs) != len(testMsgs) {
t.Fatalf("Unexpected received message count; want %d; got %d", len(testMsgs), len(msgs))
}
for i, msg := range msgs {
if string(msg.Data()) != testMsgs[i] {
t.Fatalf("Invalid msg on index %d; expected: %s; got: %s", i, testMsgs[i], string(msg.Data()))
}
}
})
t.Run("fetch messages one by one", func(t *testing.T) {
srv := RunBasicJetStreamServer()
defer shutdownJSServerAndRemoveStorage(t, srv)
nc, err := nats.Connect(srv.ClientURL())
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
js, err := New(nc)
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
defer nc.Close()
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
s, err := js.CreateStream(ctx, StreamConfig{Name: "foo", Subjects: []string{"FOO.*"}})
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
c, err := s.AddConsumer(ctx, ConsumerConfig{AckPolicy: AckExplicitPolicy})
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
msgs := make([]Msg, 0)
wg := &sync.WaitGroup{}
wg.Add(len(testMsgs))
l, err := c.Consume(func(msg Msg) {
msgs = append(msgs, msg)
wg.Done()
}, PullMaxMessages(1))
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
defer l.Stop()
publishTestMsgs(t, nc)
wg.Wait()
if len(msgs) != len(testMsgs) {
t.Fatalf("Unexpected received message count; want %d; got %d", len(testMsgs), len(msgs))
}
for i, msg := range msgs {
if string(msg.Data()) != testMsgs[i] {
t.Fatalf("Invalid msg on index %d; expected: %s; got: %s", i, testMsgs[i], string(msg.Data()))
}
}
})
t.Run("with custom max bytes", func(t *testing.T) {
srv := RunBasicJetStreamServer()
defer shutdownJSServerAndRemoveStorage(t, srv)
nc, err := nats.Connect(srv.ClientURL())
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
js, err := New(nc)
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
defer nc.Close()
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
s, err := js.CreateStream(ctx, StreamConfig{Name: "foo", Subjects: []string{"FOO.*"}})
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
c, err := s.AddConsumer(ctx, ConsumerConfig{AckPolicy: AckExplicitPolicy})
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
// subscribe to next request subject to verify how many next requests were sent
sub, err := nc.SubscribeSync(fmt.Sprintf("$JS.API.CONSUMER.MSG.NEXT.foo.%s", c.CachedInfo().Name))
if err != nil {
t.Fatalf("Error on subscribe: %v", err)
}
publishTestMsgs(t, nc)
msgs := make([]Msg, 0)
wg := &sync.WaitGroup{}
wg.Add(len(testMsgs))
l, err := c.Consume(func(msg Msg) {
msgs = append(msgs, msg)
wg.Done()
}, PullMaxBytes(150))
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
defer l.Stop()
wg.Wait()
requestsNum, _, err := sub.Pending()
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
// new request should be sent after each consumed message (msg size is 57)
if requestsNum < 3 {
t.Fatalf("Unexpected number of requests sent; want at least 5; got %d", requestsNum)
}
if len(msgs) != len(testMsgs) {
t.Fatalf("Unexpected received message count; want %d; got %d", len(testMsgs), len(msgs))
}
for i, msg := range msgs {
if string(msg.Data()) != testMsgs[i] {
t.Fatalf("Invalid msg on index %d; expected: %s; got: %s", i, testMsgs[i], string(msg.Data()))
}
}
})
t.Run("with invalid batch size", func(t *testing.T) {
srv := RunBasicJetStreamServer()
defer shutdownJSServerAndRemoveStorage(t, srv)
nc, err := nats.Connect(srv.ClientURL())
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
js, err := New(nc)
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
defer nc.Close()
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
s, err := js.CreateStream(ctx, StreamConfig{Name: "foo", Subjects: []string{"FOO.*"}})
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
c, err := s.AddConsumer(ctx, ConsumerConfig{AckPolicy: AckExplicitPolicy})
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
_, err = c.Consume(func(_ Msg) {
}, PullMaxMessages(-1))
if err == nil || !errors.Is(err, ErrInvalidOption) {
t.Fatalf("Expected error: %v; got: %v", ErrInvalidOption, err)
}
})
t.Run("with custom expiry", func(t *testing.T) {
srv := RunBasicJetStreamServer()
defer shutdownJSServerAndRemoveStorage(t, srv)
nc, err := nats.Connect(srv.ClientURL())
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
js, err := New(nc)
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
defer nc.Close()
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
s, err := js.CreateStream(ctx, StreamConfig{Name: "foo", Subjects: []string{"FOO.*"}})
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
c, err := s.AddConsumer(ctx, ConsumerConfig{AckPolicy: AckExplicitPolicy})
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
msgs := make([]Msg, 0)
wg := &sync.WaitGroup{}
wg.Add(len(testMsgs))
l, err := c.Consume(func(msg Msg) {
msgs = append(msgs, msg)
wg.Done()
}, PullExpiry(2*time.Second))
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
defer l.Stop()
publishTestMsgs(t, nc)
wg.Wait()
if len(msgs) != len(testMsgs) {
t.Fatalf("Unexpected received message count; want %d; got %d", len(testMsgs), len(msgs))
}
for i, msg := range msgs {
if string(msg.Data()) != testMsgs[i] {
t.Fatalf("Invalid msg on index %d; expected: %s; got: %s", i, testMsgs[i], string(msg.Data()))
}
}
})
t.Run("with timeout on pull request, pending messages left", func(t *testing.T) {
srv := RunBasicJetStreamServer()
defer shutdownJSServerAndRemoveStorage(t, srv)
nc, err := nats.Connect(srv.ClientURL())
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
js, err := New(nc)
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
defer nc.Close()
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
s, err := js.CreateStream(ctx, StreamConfig{Name: "foo", Subjects: []string{"FOO.*"}})
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
c, err := s.AddConsumer(ctx, ConsumerConfig{AckPolicy: AckExplicitPolicy})
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
msgs := make([]Msg, 0)
wg := &sync.WaitGroup{}
wg.Add(4 * len(testMsgs))
l, err := c.Consume(func(msg Msg) {
msgs = append(msgs, msg)
wg.Done()
}, pullOptFunc(func(o *consumeOpts) error {
o.Expires = 50 * time.Millisecond
return nil
}))
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
defer l.Stop()
time.Sleep(60 * time.Millisecond)
// publish messages in 4 batches, with pause between each batch
// on the second pull request, we should get timeout error, which triggers another batch
publishTestMsgs(t, nc)
time.Sleep(40 * time.Millisecond)
publishTestMsgs(t, nc)
time.Sleep(40 * time.Millisecond)
publishTestMsgs(t, nc)
time.Sleep(40 * time.Millisecond)
publishTestMsgs(t, nc)
wg.Wait()
if len(msgs) != 4*len(testMsgs) {
t.Fatalf("Unexpected received message count; want %d; got %d", len(testMsgs), len(msgs))
}
})
t.Run("with invalid expiry", func(t *testing.T) {
srv := RunBasicJetStreamServer()
defer shutdownJSServerAndRemoveStorage(t, srv)
nc, err := nats.Connect(srv.ClientURL())
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
js, err := New(nc)
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
defer nc.Close()
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
s, err := js.CreateStream(ctx, StreamConfig{Name: "foo", Subjects: []string{"FOO.*"}})
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
c, err := s.AddConsumer(ctx, ConsumerConfig{AckPolicy: AckExplicitPolicy})
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
_, err = c.Consume(func(_ Msg) {
}, PullExpiry(-1))
if err == nil || !errors.Is(err, ErrInvalidOption) {
t.Fatalf("Expected error: %v; got: %v", ErrInvalidOption, err)
}
})
t.Run("with idle heartbeat", func(t *testing.T) {
srv := RunBasicJetStreamServer()
defer shutdownJSServerAndRemoveStorage(t, srv)
nc, err := nats.Connect(srv.ClientURL())
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
js, err := New(nc)
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
defer nc.Close()
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
s, err := js.CreateStream(ctx, StreamConfig{Name: "foo", Subjects: []string{"FOO.*"}})
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
c, err := s.AddConsumer(ctx, ConsumerConfig{AckPolicy: AckExplicitPolicy})
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
msgs := make([]Msg, 0)
wg := &sync.WaitGroup{}
wg.Add(len(testMsgs))
l, err := c.Consume(func(msg Msg) {
msgs = append(msgs, msg)
wg.Done()
}, PullMaxBytes(1*time.Second))
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
defer l.Stop()
publishTestMsgs(t, nc)
wg.Wait()
if len(msgs) != len(testMsgs) {
t.Fatalf("Unexpected received message count; want %d; got %d", len(testMsgs), len(msgs))
}
for i, msg := range msgs {
if string(msg.Data()) != testMsgs[i] {
t.Fatalf("Invalid msg on index %d; expected: %s; got: %s", i, testMsgs[i], string(msg.Data()))
}
}
})
t.Run("with server restart", func(t *testing.T) {
srv := RunBasicJetStreamServer()
nc, err := nats.Connect(srv.ClientURL())
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
js, err := New(nc)
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
defer nc.Close()
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
s, err := js.CreateStream(ctx, StreamConfig{Name: "foo", Subjects: []string{"FOO.*"}})
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
c, err := s.AddConsumer(ctx, ConsumerConfig{AckPolicy: AckExplicitPolicy})
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
wg := &sync.WaitGroup{}
wg.Add(2 * len(testMsgs))
msgs := make([]Msg, 0)
publishTestMsgs(t, nc)
l, err := c.Consume(func(msg Msg) {
msgs = append(msgs, msg)
wg.Done()
})
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
defer l.Stop()
time.Sleep(10 * time.Millisecond)
// restart the server
srv = restartBasicJSServer(t, srv)
defer shutdownJSServerAndRemoveStorage(t, srv)
time.Sleep(10 * time.Millisecond)
publishTestMsgs(t, nc)
wg.Wait()
})
}
func TestPullConsumerConsume_WithCluster(t *testing.T) {
testSubject := "FOO.123"
testMsgs := []string{"m1", "m2", "m3", "m4", "m5"}
publishTestMsgs := func(t *testing.T, nc *nats.Conn) {
for _, msg := range testMsgs {
if err := nc.Publish(testSubject, []byte(msg)); err != nil {
t.Fatalf("Unexpected error during publish: %s", err)
}
}
}
name := "cluster"
stream := StreamConfig{
Name: name,
Replicas: 1,
Subjects: []string{"FOO.*"},
}
t.Run("no options", func(t *testing.T) {
withJSClusterAndStream(t, name, 3, stream, func(t *testing.T, subject string, srvs ...*jsServer) {
nc, err := nats.Connect(srvs[0].ClientURL())
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
js, err := New(nc)
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
defer nc.Close()
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
s, err := js.Stream(ctx, stream.Name)
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
c, err := s.AddConsumer(ctx, ConsumerConfig{AckPolicy: AckExplicitPolicy})
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
msgs := make([]Msg, 0)
wg := &sync.WaitGroup{}
wg.Add(len(testMsgs))
l, err := c.Consume(func(msg Msg) {
msgs = append(msgs, msg)
wg.Done()
})
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
defer l.Stop()
publishTestMsgs(t, nc)
wg.Wait()
if len(msgs) != len(testMsgs) {
t.Fatalf("Unexpected received message count; want %d; got %d", len(testMsgs), len(msgs))
}
for i, msg := range msgs {
if string(msg.Data()) != testMsgs[i] {
t.Fatalf("Invalid msg on index %d; expected: %s; got: %s", i, testMsgs[i], string(msg.Data()))
}
}
})
})
t.Run("subscribe, cancel subscription, then subscribe again", func(t *testing.T) {
withJSClusterAndStream(t, name, 3, stream, func(t *testing.T, subject string, srvs ...*jsServer) {
nc, err := nats.Connect(srvs[0].ClientURL())
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
js, err := New(nc)
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
defer nc.Close()
ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second)
s, err := js.Stream(ctx, stream.Name)
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
c, err := s.AddConsumer(ctx, ConsumerConfig{AckPolicy: AckExplicitPolicy})
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
wg := sync.WaitGroup{}
wg.Add(len(testMsgs))
msgs := make([]Msg, 0)
l, err := c.Consume(func(msg Msg) {
if err := msg.Ack(); err != nil {
t.Fatalf("Unexpected error: %v", err)
}
msgs = append(msgs, msg)
if len(msgs) == 5 {
cancel()
}
wg.Done()
})
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
publishTestMsgs(t, nc)
wg.Wait()
l.Stop()
time.Sleep(10 * time.Millisecond)
wg.Add(len(testMsgs))
defer cancel()
l, err = c.Consume(func(msg Msg) {
if err := msg.Ack(); err != nil {
t.Fatalf("Unexpected error: %v", err)
}
msgs = append(msgs, msg)
wg.Done()
})
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
defer l.Stop()
publishTestMsgs(t, nc)
wg.Wait()
if len(msgs) != 2*len(testMsgs) {
t.Fatalf("Unexpected received message count; want %d; got %d", len(testMsgs), len(msgs))
}
expectedMsgs := append(testMsgs, testMsgs...)
for i, msg := range msgs {
if string(msg.Data()) != expectedMsgs[i] {
t.Fatalf("Invalid msg on index %d; expected: %s; got: %s", i, testMsgs[i], string(msg.Data()))
}
}
})
})
}