Files
nats.go/jetstream/stream_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

891 lines
24 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"
"reflect"
"strings"
"testing"
"time"
"github.com/nats-io/nats.go"
)
func TestAddConsumer(t *testing.T) {
tests := []struct {
name string
consumerConfig ConsumerConfig
shouldCreate bool
withError error
}{
{
name: "create durable pull consumer",
consumerConfig: ConsumerConfig{Durable: "dur", AckPolicy: AckExplicitPolicy},
shouldCreate: true,
},
{
name: "create ephemeral pull consumer",
consumerConfig: ConsumerConfig{AckPolicy: AckExplicitPolicy},
shouldCreate: true,
},
{
name: "consumer already exists, update",
consumerConfig: ConsumerConfig{Durable: "dur", AckPolicy: AckExplicitPolicy, Description: "test consumer"},
},
{
name: "consumer already exists, illegal update",
consumerConfig: ConsumerConfig{Durable: "dur", AckPolicy: AckNonePolicy},
withError: ErrConsumerCreate,
},
{
name: "invalid durable name",
consumerConfig: ConsumerConfig{Durable: "dur.123", AckPolicy: AckExplicitPolicy},
withError: ErrInvalidConsumerName,
},
}
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)
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
var sub *nats.Subscription
if test.consumerConfig.FilterSubject != "" {
sub, err = nc.SubscribeSync(fmt.Sprintf("$JS.API.CONSUMER.CREATE.foo.*.%s", test.consumerConfig.FilterSubject))
} else {
sub, err = nc.SubscribeSync("$JS.API.CONSUMER.CREATE.foo.*")
}
c, err := s.AddConsumer(ctx, test.consumerConfig)
if test.withError != nil {
if err == nil || !errors.Is(err, test.withError) {
t.Fatalf("Expected error: %v; got: %v", test.withError, err)
}
return
}
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
if test.shouldCreate {
if _, err := sub.NextMsgWithContext(ctx); err != nil {
t.Fatalf("Expected request on %s; got %s", sub.Subject, err)
}
}
_, err = s.Consumer(ctx, c.CachedInfo().Name)
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
})
}
}
func TestConsumer(t *testing.T) {
tests := []struct {
name string
durable string
withError error
}{
{
name: "get existing consumer",
durable: "dur",
},
{
name: "consumer does not exist",
durable: "abc",
withError: ErrConsumerNotFound,
},
{
name: "invalid durable name",
durable: "dur.123",
withError: ErrInvalidConsumerName,
},
}
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)
}
_, err = s.AddConsumer(ctx, ConsumerConfig{Durable: "dur", AckPolicy: AckAllPolicy, Description: "desc"})
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
c, err := s.Consumer(ctx, test.durable)
if test.withError != nil {
if err == nil || !errors.Is(err, test.withError) {
t.Fatalf("Expected error: %v; got: %v", test.withError, err)
}
return
}
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
if c.CachedInfo().Name != test.durable {
t.Fatalf("Unexpected consumer fetched; want: %s; got: %s", test.durable, c.CachedInfo().Name)
}
})
}
}
func TestDeleteConsumer(t *testing.T) {
tests := []struct {
name string
durable string
withError error
}{
{
name: "delete existing consumer",
durable: "dur",
},
{
name: "consumer does not exist",
durable: "dur",
withError: ErrConsumerNotFound,
},
{
name: "invalid durable name",
durable: "dur.123",
withError: ErrInvalidConsumerName,
},
}
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)
}
_, err = s.AddConsumer(ctx, ConsumerConfig{Durable: "dur", AckPolicy: AckAllPolicy, Description: "desc"})
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
err := s.DeleteConsumer(ctx, test.durable)
if test.withError != nil {
if err == nil || !errors.Is(err, test.withError) {
t.Fatalf("Expected error: %v; got: %v", test.withError, err)
}
return
}
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
_, err = s.Consumer(ctx, test.durable)
if err == nil || !errors.Is(err, ErrConsumerNotFound) {
t.Fatalf("Expected error: %v; got: %v", ErrConsumerNotFound, err)
}
})
}
}
func TestStreamInfo(t *testing.T) {
tests := []struct {
name string
subjectsFilter string
expectedSubjectMsgs map[string]uint64
deletedDetails bool
withError error
}{
{
name: "info without opts",
},
{
name: "with deleted details",
deletedDetails: true,
},
{
name: "with subjects filter, one subject",
subjectsFilter: "FOO.A",
expectedSubjectMsgs: map[string]uint64{"FOO.A": 8},
},
{
name: "with subjects filter, wildcard subject",
subjectsFilter: "FOO.*",
expectedSubjectMsgs: map[string]uint64{"FOO.A": 8, "FOO.B": 10},
},
{
name: "with subjects filter, and deleted details",
subjectsFilter: "FOO.A",
expectedSubjectMsgs: map[string]uint64{"FOO.A": 8},
},
}
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.*"}, Description: "desc"})
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
for i := 0; i < 10; i++ {
if _, err := js.Publish(ctx, "FOO.A", []byte(fmt.Sprintf("msg %d on subject A", i))); err != nil {
t.Fatalf("Unexpected error: %v", err)
}
if _, err := js.Publish(ctx, "FOO.B", []byte(fmt.Sprintf("msg %d on subject B", i))); err != nil {
t.Fatalf("Unexpected error: %v", err)
}
}
if err := s.DeleteMsg(ctx, 3); err != nil {
t.Fatalf("Unexpected error: %v", err)
}
if err := s.DeleteMsg(ctx, 5); err != nil {
t.Fatalf("Unexpected error: %v", err)
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
opts := make([]StreamInfoOpt, 0)
if test.deletedDetails {
opts = append(opts, WithDeletedDetails(test.deletedDetails))
}
if test.subjectsFilter != "" {
opts = append(opts, WithSubjectFilter(test.subjectsFilter))
}
info, err := s.Info(ctx, opts...)
if test.withError != nil {
if err == nil || !errors.Is(err, test.withError) {
t.Fatalf("Expected error: %v; got: %v", test.withError, err)
}
return
}
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
if info.Config.Description != "desc" {
t.Fatalf("Unexpected description value fetched; want: foo; got: %s", info.Config.Description)
}
if test.deletedDetails {
if info.State.NumDeleted != 2 {
t.Fatalf("Expected 2 deleted messages; got: %d", info.State.NumDeleted)
}
if len(info.State.Deleted) != 2 || !reflect.DeepEqual(info.State.Deleted, []uint64{3, 5}) {
t.Fatalf("Invalid value for deleted details; want: [3 5] got: %v", info.State.Deleted)
}
}
if test.subjectsFilter != "" {
if !reflect.DeepEqual(test.expectedSubjectMsgs, info.State.Subjects) {
t.Fatalf("Invalid value for subjects filter; want: %v; got: %v", test.expectedSubjectMsgs, info.State.Subjects)
}
}
})
}
}
func TestStreamCachedInfo(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.*"},
Description: "desc",
})
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
info := s.CachedInfo()
if info.Config.Name != "foo" {
t.Fatalf("Invalid stream name; expected: 'foo'; got: %s", info.Config.Name)
}
if info.Config.Description != "desc" {
t.Fatalf("Invalid stream description; expected: 'desc'; got: %s", info.Config.Description)
}
// update consumer and see if info is updated
_, err = js.UpdateStream(ctx, StreamConfig{
Name: "foo",
Subjects: []string{"FOO.*"},
Description: "updated desc",
})
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
info = s.CachedInfo()
if info.Config.Name != "foo" {
t.Fatalf("Invalid stream name; expected: 'foo'; got: %s", info.Config.Name)
}
// description should not be updated when using cached values
if info.Config.Description != "desc" {
t.Fatalf("Invalid stream description; expected: 'updated desc'; got: %s", info.Config.Description)
}
}
func TestGetMsg(t *testing.T) {
tests := []struct {
name string
seq uint64
expectedData string
expectedHeaders nats.Header
withError error
}{
{
name: "get existing msg",
seq: 2,
expectedData: "msg 1 on subject B",
},
{
name: "get deleted msg",
seq: 3,
withError: ErrMsgNotFound,
},
{
name: "get non existing msg",
seq: 50,
withError: ErrMsgNotFound,
},
{
name: "get msg with headers",
seq: 9,
expectedData: "msg with headers",
expectedHeaders: map[string][]string{
"X-Nats-Test-Data": {"test_data"},
"X-Nats-Key": {"123"},
},
},
}
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.*"}, Description: "desc"})
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
for i := 1; i < 5; i++ {
if _, err := js.Publish(ctx, "FOO.A", []byte(fmt.Sprintf("msg %d on subject A", i))); err != nil {
t.Fatalf("Unexpected error: %v", err)
}
if _, err := js.Publish(ctx, "FOO.B", []byte(fmt.Sprintf("msg %d on subject B", i))); err != nil {
t.Fatalf("Unexpected error: %v", err)
}
}
if _, err := js.PublishMsg(ctx, &nats.Msg{
Data: []byte("msg with headers"),
Header: map[string][]string{
"X-Nats-Test-Data": {"test_data"},
"X-Nats-Key": {"123"},
},
Subject: "FOO.C",
}); err != nil {
t.Fatalf("Unexpected error: %v", err)
}
if err := s.DeleteMsg(ctx, 3); err != nil {
t.Fatalf("Unexpected error: %v", err)
}
if err := s.DeleteMsg(ctx, 5); err != nil {
t.Fatalf("Unexpected error: %v", err)
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
msg, err := s.GetMsg(ctx, test.seq)
if test.withError != nil {
if err == nil || !errors.Is(err, test.withError) {
t.Fatalf("Expected error: %v; got: %v", test.withError, err)
}
return
}
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
if string(msg.Data) != test.expectedData {
t.Fatalf("Invalid message data; want: %s; got: %s", test.expectedData, string(msg.Data))
}
if !reflect.DeepEqual(msg.Header, test.expectedHeaders) {
t.Fatalf("Invalid message headers; want: %v; got: %v", test.expectedHeaders, msg.Header)
}
})
}
}
func TestDeleteMsg(t *testing.T) {
tests := []struct {
name string
seq uint64
withError error
}{
{
name: "delete message",
seq: 3,
},
{
name: "msg not found",
seq: 10,
withError: ErrMsgDeleteUnsuccessful,
},
}
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.*"}, Description: "desc"})
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
for i := 1; i < 5; i++ {
if _, err := js.Publish(ctx, "FOO.A", []byte(fmt.Sprintf("msg %d on subject A", i))); err != nil {
t.Fatalf("Unexpected error: %v", err)
}
if _, err := js.Publish(ctx, "FOO.B", []byte(fmt.Sprintf("msg %d on subject B", i))); err != nil {
t.Fatalf("Unexpected error: %v", err)
}
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
sub, err := nc.SubscribeSync("$JS.API.STREAM.MSG.DELETE.foo")
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
err = s.DeleteMsg(ctx, test.seq)
if test.withError != nil {
if err == nil || !errors.Is(err, test.withError) {
t.Fatalf("Expected error: %v; got: %v", test.withError, err)
}
return
}
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
deleteMsg, err := sub.NextMsgWithContext(ctx)
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
if !strings.Contains(string(deleteMsg.Data), `"no_erase":true`) {
t.Fatalf("Expected no_erase on request; got: %q", string(deleteMsg.Data))
}
if _, err = s.GetMsg(ctx, test.seq); err == nil || !errors.Is(err, ErrMsgNotFound) {
t.Fatalf("Expected error: %v; got: %v", ErrMsgNotFound, err)
}
})
}
}
func TestSecureDeleteMsg(t *testing.T) {
tests := []struct {
name string
seq uint64
withError error
}{
{
name: "delete message",
seq: 3,
},
{
name: "msg not found",
seq: 10,
withError: ErrMsgDeleteUnsuccessful,
},
}
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.*"}, Description: "desc"})
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
for i := 1; i < 5; i++ {
if _, err := js.Publish(ctx, "FOO.A", []byte(fmt.Sprintf("msg %d on subject A", i))); err != nil {
t.Fatalf("Unexpected error: %v", err)
}
if _, err := js.Publish(ctx, "FOO.B", []byte(fmt.Sprintf("msg %d on subject B", i))); err != nil {
t.Fatalf("Unexpected error: %v", err)
}
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
sub, err := nc.SubscribeSync("$JS.API.STREAM.MSG.DELETE.foo")
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
err = s.SecureDeleteMsg(ctx, test.seq)
if test.withError != nil {
if err == nil || !errors.Is(err, test.withError) {
t.Fatalf("Expected error: %v; got: %v", test.withError, err)
}
return
}
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
deleteMsg, err := sub.NextMsgWithContext(ctx)
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
if strings.Contains(string(deleteMsg.Data), `"no_erase":true`) {
t.Fatalf("Expected no_erase to be set to false on request; got: %q", string(deleteMsg.Data))
}
if _, err = s.GetMsg(ctx, test.seq); err == nil || !errors.Is(err, ErrMsgNotFound) {
t.Fatalf("Expected error: %v; got: %v", ErrMsgNotFound, err)
}
})
}
}
func TestListConsumers(t *testing.T) {
tests := []struct {
name string
consumersNum int
withError error
}{
{
name: "list consumers",
consumersNum: 500,
},
{
name: "no consumers available",
consumersNum: 0,
},
}
for _, test := range tests {
t.Run(test.name, 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)
}
for i := 0; i < test.consumersNum; i++ {
_, err = s.AddConsumer(ctx, ConsumerConfig{AckPolicy: AckExplicitPolicy})
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
}
consumersList := s.ListConsumers(ctx)
consumers := make([]*ConsumerInfo, 0)
Loop:
for {
select {
case s := <-consumersList.Info():
consumers = append(consumers, s)
case err := <-consumersList.Err():
if !errors.Is(err, ErrEndOfData) {
t.Fatalf("Unexpected error: %v", err)
}
break Loop
}
}
if len(consumers) != test.consumersNum {
t.Fatalf("Wrong number of streams; want: %d; got: %d", test.consumersNum, len(consumers))
}
})
}
}
func TestConsumerNames(t *testing.T) {
tests := []struct {
name string
consumersNum int
withError error
}{
{
name: "list consumer names",
consumersNum: 500,
},
{
name: "no consumers available",
consumersNum: 0,
},
}
for _, test := range tests {
t.Run(test.name, 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)
}
for i := 0; i < test.consumersNum; i++ {
_, err = s.AddConsumer(ctx, ConsumerConfig{AckPolicy: AckExplicitPolicy})
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
}
consumersList := s.ConsumerNames(ctx)
consumers := make([]string, 0)
Loop:
for {
select {
case s := <-consumersList.Name():
consumers = append(consumers, s)
case err := <-consumersList.Err():
if !errors.Is(err, ErrEndOfData) {
t.Fatalf("Unexpected error: %v", err)
}
break Loop
}
}
if len(consumers) != test.consumersNum {
t.Fatalf("Wrong number of streams; want: %d; got: %d", test.consumersNum, len(consumers))
}
})
}
}
func TestPurgeStream(t *testing.T) {
tests := []struct {
name string
opts []StreamPurgeOpt
expectedSeq []uint64
withError error
}{
{
name: "purge all messages",
expectedSeq: []uint64{},
},
{
name: "purge on subject",
opts: []StreamPurgeOpt{WithPurgeSubject("FOO.2")},
expectedSeq: []uint64{1, 3, 5, 7, 9},
},
{
name: "purge with sequence",
opts: []StreamPurgeOpt{WithPurgeSequence(5)},
expectedSeq: []uint64{5, 6, 7, 8, 9, 10},
},
{
name: "purge with keep",
opts: []StreamPurgeOpt{WithPurgeKeep(3)},
expectedSeq: []uint64{8, 9, 10},
},
{
name: "purge with filter and sequence",
opts: []StreamPurgeOpt{WithPurgeSubject("FOO.2"), WithPurgeSequence(8)},
expectedSeq: []uint64{1, 3, 5, 7, 8, 9, 10},
},
{
name: "purge with filter and keep",
opts: []StreamPurgeOpt{WithPurgeSubject("FOO.2"), WithPurgeKeep(3)},
expectedSeq: []uint64{1, 3, 5, 6, 7, 8, 9, 10},
},
{
name: "with sequence and keep",
opts: []StreamPurgeOpt{WithPurgeSequence(5), WithPurgeKeep(3)},
withError: ErrInvalidOption,
},
}
for _, test := range tests {
t.Run(test.name, 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(), 20*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)
}
for i := 0; i < 5; i++ {
if _, err := js.Publish(ctx, "FOO.1", []byte(fmt.Sprintf("msg %d on FOO.1", i))); err != nil {
t.Fatalf("Unexpected error: %v", err)
}
if _, err := js.Publish(ctx, "FOO.2", []byte(fmt.Sprintf("msg %d on FOO.2", i))); err != nil {
t.Fatalf("Unexpected error: %v", err)
}
}
err = s.Purge(ctx, test.opts...)
if test.withError != nil {
if err == nil || !errors.Is(err, test.withError) {
t.Fatalf("Expected error: %v; got: %v", test.withError, err)
}
return
}
c, err := s.AddConsumer(ctx, ConsumerConfig{AckPolicy: AckExplicitPolicy})
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
seqs := make([]uint64, 0)
Loop:
for {
msgs, err := c.FetchNoWait(1)
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
msg := <-msgs.Messages()
if msg == nil {
break Loop
}
if err := msgs.Error(); err != nil {
t.Fatalf("unexpected error during fetch: %v", err)
}
meta, err := msg.Metadata()
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
seqs = append(seqs, meta.Sequence.Stream)
}
if !reflect.DeepEqual(seqs, test.expectedSeq) {
t.Fatalf("Invalid result; want: %v; got: %v", test.expectedSeq, seqs)
}
})
}
}