mirror of
https://github.com/nats-io/nats.go.git
synced 2025-10-28 02:42:14 +08:00
891 lines
24 KiB
Go
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)
|
|
}
|
|
})
|
|
}
|
|
}
|