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

1236 lines
28 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"
"os"
"reflect"
"testing"
"time"
"github.com/nats-io/nats-server/v2/server"
"github.com/nats-io/nats.go"
)
func TestPublishMsg(t *testing.T) {
type publishConfig struct {
msg *nats.Msg
opts []PublishOpt
expectedHeaders nats.Header
expectedAck PubAck
withError func(*testing.T, error)
}
tests := []struct {
name string
srvConfig []byte
timeout time.Duration
msgs []publishConfig
}{
{
name: "publish 3 simple messages, no opts",
msgs: []publishConfig{
{
msg: &nats.Msg{
Data: []byte("msg 1"),
Subject: "FOO.1",
},
expectedAck: PubAck{
Stream: "foo",
Sequence: 1,
Domain: "",
},
},
{
msg: &nats.Msg{
Data: []byte("msg 2"),
Subject: "FOO.1",
},
expectedAck: PubAck{
Stream: "foo",
Sequence: 2,
Domain: "",
},
},
{
msg: &nats.Msg{
Data: []byte("msg 3"),
Subject: "FOO.2",
},
expectedAck: PubAck{
Stream: "foo",
Sequence: 3,
Domain: "",
},
},
},
},
{
name: "publish 3 messages with message ID, with duplicate",
msgs: []publishConfig{
{
msg: &nats.Msg{
Data: []byte("msg 1"),
Subject: "FOO.1",
},
opts: []PublishOpt{WithMsgID("1")},
expectedAck: PubAck{
Stream: "foo",
Sequence: 1,
},
expectedHeaders: nats.Header{
"Nats-Msg-Id": []string{"1"},
},
},
{
msg: &nats.Msg{
Data: []byte("msg 2"),
Subject: "FOO.1",
},
opts: []PublishOpt{WithMsgID("1")},
expectedAck: PubAck{
Stream: "foo",
Sequence: 1,
Duplicate: true,
},
expectedHeaders: nats.Header{
"Nats-Msg-Id": []string{"1"},
},
},
{
msg: &nats.Msg{
Data: []byte("msg 3"),
Subject: "FOO.2",
},
opts: []PublishOpt{WithMsgID("2")},
expectedAck: PubAck{
Stream: "foo",
Sequence: 2,
},
expectedHeaders: nats.Header{
"Nats-Msg-Id": []string{"2"},
},
},
},
},
{
name: "expect last msg ID",
msgs: []publishConfig{
{
msg: &nats.Msg{
Data: []byte("msg 1"),
Subject: "FOO.1",
},
opts: []PublishOpt{WithMsgID("1")},
expectedAck: PubAck{
Stream: "foo",
Sequence: 1,
},
expectedHeaders: nats.Header{
"Nats-Msg-Id": []string{"1"},
},
},
{
msg: &nats.Msg{
Data: []byte("msg 2"),
Subject: "FOO.1",
},
opts: []PublishOpt{WithMsgID("2")},
expectedAck: PubAck{
Stream: "foo",
Sequence: 2,
},
expectedHeaders: nats.Header{
"Nats-Msg-Id": []string{"2"},
},
},
{
msg: &nats.Msg{
Data: []byte("msg 3"),
Subject: "FOO.2",
},
opts: []PublishOpt{WithExpectLastMsgID("2")},
expectedAck: PubAck{
Stream: "foo",
Sequence: 3,
},
expectedHeaders: nats.Header{
"Nats-Expected-Last-Msg-Id": []string{"2"},
},
},
},
},
{
name: "invalid last msg id",
msgs: []publishConfig{
{
msg: &nats.Msg{
Data: []byte("msg 1"),
Subject: "FOO.1",
},
opts: []PublishOpt{WithMsgID("1")},
expectedAck: PubAck{
Stream: "foo",
Sequence: 1,
},
expectedHeaders: nats.Header{
"Nats-Msg-Id": []string{"1"},
},
},
{
msg: &nats.Msg{
Data: []byte("msg 2"),
Subject: "FOO.2",
},
opts: []PublishOpt{WithExpectLastMsgID("abc")},
withError: func(t *testing.T, err error) {
var apiErr *APIError
if ok := errors.As(err, &apiErr); !ok {
t.Fatalf("Expected API error; got: %v", err)
}
if apiErr.ErrorCode != 10070 {
t.Fatalf("Expected error code: 10070; got: %d", apiErr.ErrorCode)
}
},
},
},
},
{
name: "expect last sequence",
msgs: []publishConfig{
{
msg: &nats.Msg{
Data: []byte("msg 1"),
Subject: "FOO.1",
},
expectedAck: PubAck{
Stream: "foo",
Sequence: 1,
},
},
{
msg: &nats.Msg{
Data: []byte("msg 2"),
Subject: "FOO.1",
},
opts: []PublishOpt{},
expectedAck: PubAck{
Stream: "foo",
Sequence: 2,
},
},
{
msg: &nats.Msg{
Data: []byte("msg 3"),
Subject: "FOO.2",
},
opts: []PublishOpt{WithExpectLastSequence(2)},
expectedAck: PubAck{
Stream: "foo",
Sequence: 3,
},
expectedHeaders: nats.Header{
"Nats-Expected-Last-Sequence": []string{"2"},
},
},
},
},
{
name: "invalid last seq",
msgs: []publishConfig{
{
msg: &nats.Msg{
Data: []byte("msg 1"),
Subject: "FOO.1",
},
opts: []PublishOpt{},
expectedAck: PubAck{
Stream: "foo",
Sequence: 1,
},
},
{
msg: &nats.Msg{
Data: []byte("msg 2"),
Subject: "FOO.2",
},
opts: []PublishOpt{WithExpectLastSequence(123)},
withError: func(t *testing.T, err error) {
var apiErr *APIError
if ok := errors.As(err, &apiErr); !ok {
t.Fatalf("Expected API error; got: %v", err)
}
if apiErr.ErrorCode != 10071 {
t.Fatalf("Expected error code: 10071; got: %d", apiErr.ErrorCode)
}
},
},
},
},
{
name: "expect last sequence per subject",
msgs: []publishConfig{
{
msg: &nats.Msg{
Data: []byte("msg 1"),
Subject: "FOO.1",
},
expectedAck: PubAck{
Stream: "foo",
Sequence: 1,
},
},
{
msg: &nats.Msg{
Data: []byte("msg 2"),
Subject: "FOO.2",
},
opts: []PublishOpt{},
expectedAck: PubAck{
Stream: "foo",
Sequence: 2,
},
},
{
msg: &nats.Msg{
Data: []byte("msg 3"),
Subject: "FOO.1",
},
opts: []PublishOpt{WithExpectLastSequencePerSubject(1)},
expectedAck: PubAck{
Stream: "foo",
Sequence: 3,
},
expectedHeaders: nats.Header{
"Nats-Expected-Last-Subject-Sequence": []string{"1"},
},
},
},
},
{
name: "invalid last sequence per subject",
msgs: []publishConfig{
{
msg: &nats.Msg{
Data: []byte("msg 1"),
Subject: "FOO.1",
},
expectedAck: PubAck{
Stream: "foo",
Sequence: 1,
},
},
{
msg: &nats.Msg{
Data: []byte("msg 2"),
Subject: "FOO.2",
},
opts: []PublishOpt{},
expectedAck: PubAck{
Stream: "foo",
Sequence: 2,
},
},
{
msg: &nats.Msg{
Data: []byte("msg 3"),
Subject: "FOO.1",
},
opts: []PublishOpt{WithExpectLastSequencePerSubject(123)},
withError: func(t *testing.T, err error) {
var apiErr *APIError
if ok := errors.As(err, &apiErr); !ok {
t.Fatalf("Expected API error; got: %v", err)
}
if apiErr.ErrorCode != 10071 {
t.Fatalf("Expected error code: 10071; got: %d", apiErr.ErrorCode)
}
},
},
},
},
{
name: "expect stream header",
msgs: []publishConfig{
{
msg: &nats.Msg{
Data: []byte("msg 1"),
Subject: "FOO.1",
},
opts: []PublishOpt{WithExpectStream("foo")},
expectedAck: PubAck{
Stream: "foo",
Sequence: 1,
},
expectedHeaders: nats.Header{
"Nats-Expected-Stream": []string{"foo"},
},
},
},
},
{
name: "invalid expected stream header",
msgs: []publishConfig{
{
msg: &nats.Msg{
Data: []byte("msg 1"),
Subject: "FOO.1",
},
opts: []PublishOpt{WithExpectStream("abc")},
withError: func(t *testing.T, err error) {
var apiErr *APIError
if ok := errors.As(err, &apiErr); !ok {
t.Fatalf("Expected API error; got: %v", err)
}
if apiErr.ErrorCode != 10060 {
t.Fatalf("Expected error code: 10060; got: %d", apiErr.ErrorCode)
}
},
},
},
},
{
name: "publish 3 simple messages with domain set",
srvConfig: []byte(
`
listen: 127.0.0.1:-1
jetstream: {domain: "test-domain"}
`),
msgs: []publishConfig{
{
msg: &nats.Msg{
Data: []byte("msg 1"),
Subject: "FOO.1",
},
expectedAck: PubAck{
Stream: "foo",
Sequence: 1,
Domain: "test-domain",
},
},
{
msg: &nats.Msg{
Data: []byte("msg 2"),
Subject: "FOO.1",
},
expectedAck: PubAck{
Stream: "foo",
Sequence: 2,
Domain: "test-domain",
},
},
{
msg: &nats.Msg{
Data: []byte("msg 3"),
Subject: "FOO.2",
},
expectedAck: PubAck{
Stream: "foo",
Sequence: 3,
Domain: "test-domain",
},
},
},
},
{
name: "publish timeout",
msgs: []publishConfig{
{
msg: &nats.Msg{
Data: []byte("msg 1"),
Subject: "FOO.1",
},
opts: []PublishOpt{},
expectedAck: PubAck{
Stream: "foo",
Sequence: 1,
},
expectedHeaders: nats.Header{
"Nats-Expected-Stream": []string{"foo"},
},
withError: func(t *testing.T, err error) {
if !errors.Is(err, context.DeadlineExceeded) {
t.Fatalf("Expected deadline exceeded error; got: %v", err)
}
},
},
},
timeout: 1 * time.Nanosecond,
},
{
name: "invalid option set",
msgs: []publishConfig{
{
msg: &nats.Msg{
Data: []byte("msg 1"),
Subject: "FOO.1",
},
opts: []PublishOpt{WithStallWait(1 * time.Second)},
withError: func(t *testing.T, err error) {
if !errors.Is(err, ErrInvalidOption) {
t.Fatalf("Expected error: %v; got: %v", ErrInvalidOption, err)
}
},
},
},
},
{
name: "no subject set on message",
msgs: []publishConfig{
{
msg: &nats.Msg{
Data: []byte("msg 1"),
},
opts: []PublishOpt{},
withError: func(t *testing.T, err error) {
if !errors.Is(err, nats.ErrBadSubject) {
t.Fatalf("Expected error: %v; got: %v", nats.ErrBadSubject, err)
}
},
},
},
},
{
name: "invalid subject set",
msgs: []publishConfig{
{
msg: &nats.Msg{
Data: []byte("msg 1"),
Subject: "ABC",
},
opts: []PublishOpt{},
withError: func(t *testing.T, err error) {
if !errors.Is(err, ErrNoStreamResponse) {
t.Fatalf("Expected error: %v; got: %v", nats.ErrNoStreamResponse, err)
}
},
},
},
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
var srv *server.Server
if test.srvConfig != nil {
conf := createConfFile(t, test.srvConfig)
defer os.Remove(conf)
srv, _ = RunServerWithConfig(conf)
} else {
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()
_, err = js.CreateStream(ctx, StreamConfig{Name: "foo", Subjects: []string{"FOO.*"}, MaxMsgSize: 64})
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
for _, pub := range test.msgs {
var pubCtx context.Context
var pubCancel context.CancelFunc
if test.timeout != 0 {
pubCtx, pubCancel = context.WithTimeout(ctx, test.timeout)
} else {
pubCtx, pubCancel = context.WithTimeout(ctx, 1*time.Minute)
}
ack, err := js.PublishMsg(pubCtx, pub.msg, pub.opts...)
pubCancel()
if pub.withError != nil {
pub.withError(t, err)
continue
}
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
if !reflect.DeepEqual(pub.expectedHeaders, pub.msg.Header) {
t.Fatalf("Invalid headers on message; want: %v; got: %v", pub.expectedHeaders, pub.msg.Header)
}
if *ack != pub.expectedAck {
t.Fatalf("Invalid ack received; want: %v; got: %v", pub.expectedAck, ack)
}
}
})
}
}
func TestPublish(t *testing.T) {
// Only very basic test cases, as most use cases are tested in TestPublishMsg
tests := []struct {
name string
msg []byte
subject string
opts []PublishOpt
withError error
}{
{
name: "publish single message on stream, no options",
msg: []byte("msg"),
subject: "FOO.1",
},
{
name: "publish single message on stream with message id",
msg: []byte("msg"),
subject: "FOO.1",
opts: []PublishOpt{WithMsgID("1")},
},
{
name: "empty subject passed",
msg: []byte("msg"),
subject: "",
withError: nats.ErrBadSubject,
},
}
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)
}
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()
_, err = js.CreateStream(ctx, StreamConfig{Name: "foo", Subjects: []string{"FOO.*"}, MaxMsgSize: 64})
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
ack, err := js.Publish(ctx, test.subject, test.msg, 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
}
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
if ack.Sequence != 1 || ack.Stream != "foo" {
t.Fatalf("Invalid ack; want sequence 1 on stream foo, got: %v", ack)
}
})
}
}
func TestPublishMsgAsync(t *testing.T) {
type publishConfig struct {
msg *nats.Msg
opts []PublishOpt
expectedHeaders nats.Header
expectedAck PubAck
withAckError func(*testing.T, error)
withPublishError func(*testing.T, error)
}
tests := []struct {
name string
msgs []publishConfig
srvConfig []byte
timeout time.Duration
}{
{
name: "publish 3 simple messages, no opts",
msgs: []publishConfig{
{
msg: &nats.Msg{
Data: []byte("msg 1"),
Subject: "FOO.1",
},
expectedAck: PubAck{
Stream: "foo",
Sequence: 1,
Domain: "",
},
},
{
msg: &nats.Msg{
Data: []byte("msg 2"),
Subject: "FOO.1",
},
expectedAck: PubAck{
Stream: "foo",
Sequence: 2,
Domain: "",
},
},
{
msg: &nats.Msg{
Data: []byte("msg 3"),
Subject: "FOO.2",
},
expectedAck: PubAck{
Stream: "foo",
Sequence: 3,
Domain: "",
},
},
},
},
{
name: "publish 3 messages with message ID, with duplicate",
msgs: []publishConfig{
{
msg: &nats.Msg{
Data: []byte("msg 1"),
Subject: "FOO.1",
},
opts: []PublishOpt{WithMsgID("1")},
expectedAck: PubAck{
Stream: "foo",
Sequence: 1,
},
expectedHeaders: nats.Header{
"Nats-Msg-Id": []string{"1"},
},
},
{
msg: &nats.Msg{
Data: []byte("msg 2"),
Subject: "FOO.1",
},
opts: []PublishOpt{WithMsgID("1")},
expectedAck: PubAck{
Stream: "foo",
Sequence: 1,
Duplicate: true,
},
expectedHeaders: nats.Header{
"Nats-Msg-Id": []string{"1"},
},
},
{
msg: &nats.Msg{
Data: []byte("msg 3"),
Subject: "FOO.2",
},
opts: []PublishOpt{WithMsgID("2")},
expectedAck: PubAck{
Stream: "foo",
Sequence: 2,
},
expectedHeaders: nats.Header{
"Nats-Msg-Id": []string{"2"},
},
},
},
},
{
name: "expect last msg ID",
msgs: []publishConfig{
{
msg: &nats.Msg{
Data: []byte("msg 1"),
Subject: "FOO.1",
},
opts: []PublishOpt{WithMsgID("1")},
expectedAck: PubAck{
Stream: "foo",
Sequence: 1,
},
expectedHeaders: nats.Header{
"Nats-Msg-Id": []string{"1"},
},
},
{
msg: &nats.Msg{
Data: []byte("msg 2"),
Subject: "FOO.1",
},
opts: []PublishOpt{WithMsgID("2")},
expectedAck: PubAck{
Stream: "foo",
Sequence: 2,
},
expectedHeaders: nats.Header{
"Nats-Msg-Id": []string{"2"},
},
},
{
msg: &nats.Msg{
Data: []byte("msg 3"),
Subject: "FOO.2",
},
opts: []PublishOpt{WithExpectLastMsgID("2")},
expectedAck: PubAck{
Stream: "foo",
Sequence: 3,
},
expectedHeaders: nats.Header{
"Nats-Expected-Last-Msg-Id": []string{"2"},
},
},
},
},
{
name: "invalid last msg id",
msgs: []publishConfig{
{
msg: &nats.Msg{
Data: []byte("msg 1"),
Subject: "FOO.1",
},
opts: []PublishOpt{WithMsgID("1")},
expectedAck: PubAck{
Stream: "foo",
Sequence: 1,
},
expectedHeaders: nats.Header{
"Nats-Msg-Id": []string{"1"},
},
},
{
msg: &nats.Msg{
Data: []byte("msg 2"),
Subject: "FOO.2",
},
opts: []PublishOpt{WithExpectLastMsgID("abc")},
withAckError: func(t *testing.T, err error) {
var apiErr *APIError
if ok := errors.As(err, &apiErr); !ok {
t.Fatalf("Expected API error; got: %v", err)
}
if apiErr.ErrorCode != 10070 {
t.Fatalf("Expected error code: 10070; got: %d", apiErr.ErrorCode)
}
},
},
},
},
{
name: "expect last sequence",
msgs: []publishConfig{
{
msg: &nats.Msg{
Data: []byte("msg 1"),
Subject: "FOO.1",
},
expectedAck: PubAck{
Stream: "foo",
Sequence: 1,
},
},
{
msg: &nats.Msg{
Data: []byte("msg 2"),
Subject: "FOO.1",
},
opts: []PublishOpt{},
expectedAck: PubAck{
Stream: "foo",
Sequence: 2,
},
},
{
msg: &nats.Msg{
Data: []byte("msg 3"),
Subject: "FOO.2",
},
opts: []PublishOpt{WithExpectLastSequence(2)},
expectedAck: PubAck{
Stream: "foo",
Sequence: 3,
},
expectedHeaders: nats.Header{
"Nats-Expected-Last-Sequence": []string{"2"},
},
},
},
},
{
name: "invalid last seq",
msgs: []publishConfig{
{
msg: &nats.Msg{
Data: []byte("msg 1"),
Subject: "FOO.1",
},
opts: []PublishOpt{},
expectedAck: PubAck{
Stream: "foo",
Sequence: 1,
},
},
{
msg: &nats.Msg{
Data: []byte("msg 2"),
Subject: "FOO.2",
},
opts: []PublishOpt{WithExpectLastSequence(123)},
withAckError: func(t *testing.T, err error) {
var apiErr *APIError
if ok := errors.As(err, &apiErr); !ok {
t.Fatalf("Expected API error; got: %v", err)
}
if apiErr.ErrorCode != 10071 {
t.Fatalf("Expected error code: 10071; got: %d", apiErr.ErrorCode)
}
},
},
},
},
{
name: "expect last sequence per subject",
msgs: []publishConfig{
{
msg: &nats.Msg{
Data: []byte("msg 1"),
Subject: "FOO.1",
},
expectedAck: PubAck{
Stream: "foo",
Sequence: 1,
},
},
{
msg: &nats.Msg{
Data: []byte("msg 2"),
Subject: "FOO.2",
},
opts: []PublishOpt{},
expectedAck: PubAck{
Stream: "foo",
Sequence: 2,
},
},
{
msg: &nats.Msg{
Data: []byte("msg 3"),
Subject: "FOO.1",
},
opts: []PublishOpt{WithExpectLastSequencePerSubject(1)},
expectedAck: PubAck{
Stream: "foo",
Sequence: 3,
},
expectedHeaders: nats.Header{
"Nats-Expected-Last-Subject-Sequence": []string{"1"},
},
},
},
},
{
name: "invalid last sequence per subject",
msgs: []publishConfig{
{
msg: &nats.Msg{
Data: []byte("msg 1"),
Subject: "FOO.1",
},
expectedAck: PubAck{
Stream: "foo",
Sequence: 1,
},
},
{
msg: &nats.Msg{
Data: []byte("msg 2"),
Subject: "FOO.2",
},
opts: []PublishOpt{},
expectedAck: PubAck{
Stream: "foo",
Sequence: 2,
},
},
{
msg: &nats.Msg{
Data: []byte("msg 3"),
Subject: "FOO.1",
},
opts: []PublishOpt{WithExpectLastSequencePerSubject(123)},
withAckError: func(t *testing.T, err error) {
var apiErr *APIError
if ok := errors.As(err, &apiErr); !ok {
t.Fatalf("Expected API error; got: %v", err)
}
if apiErr.ErrorCode != 10071 {
t.Fatalf("Expected error code: 10071; got: %d", apiErr.ErrorCode)
}
},
},
},
},
{
name: "expect stream header",
msgs: []publishConfig{
{
msg: &nats.Msg{
Data: []byte("msg 1"),
Subject: "FOO.1",
},
opts: []PublishOpt{WithExpectStream("foo")},
expectedAck: PubAck{
Stream: "foo",
Sequence: 1,
},
expectedHeaders: nats.Header{
"Nats-Expected-Stream": []string{"foo"},
},
},
},
},
{
name: "invalid expected stream header",
msgs: []publishConfig{
{
msg: &nats.Msg{
Data: []byte("msg 1"),
Subject: "FOO.1",
},
opts: []PublishOpt{WithExpectStream("abc")},
withAckError: func(t *testing.T, err error) {
var apiErr *APIError
if ok := errors.As(err, &apiErr); !ok {
t.Fatalf("Expected API error; got: %v", err)
}
if apiErr.ErrorCode != 10060 {
t.Fatalf("Expected error code: 10060; got: %d", apiErr.ErrorCode)
}
},
},
},
},
{
name: "publish 3 simple messages with domain set",
srvConfig: []byte(
`
listen: 127.0.0.1:-1
jetstream: {domain: "test-domain"}
`),
msgs: []publishConfig{
{
msg: &nats.Msg{
Data: []byte("msg 1"),
Subject: "FOO.1",
},
expectedAck: PubAck{
Stream: "foo",
Sequence: 1,
Domain: "test-domain",
},
},
{
msg: &nats.Msg{
Data: []byte("msg 2"),
Subject: "FOO.1",
},
expectedAck: PubAck{
Stream: "foo",
Sequence: 2,
Domain: "test-domain",
},
},
{
msg: &nats.Msg{
Data: []byte("msg 3"),
Subject: "FOO.2",
},
expectedAck: PubAck{
Stream: "foo",
Sequence: 3,
Domain: "test-domain",
},
},
},
},
{
name: "invalid subject set",
msgs: []publishConfig{
{
msg: &nats.Msg{
Data: []byte("msg 1"),
Subject: "ABC",
},
withAckError: func(t *testing.T, err error) {
if !errors.Is(err, nats.ErrNoResponders) {
t.Fatalf("Expected error: %v; got: %v", nats.ErrNoResponders, err)
}
},
},
},
},
{
name: "reply subject set",
msgs: []publishConfig{
{
msg: &nats.Msg{
Data: []byte("msg 1"),
Subject: "FOO.1",
Reply: "BAR",
},
withPublishError: func(t *testing.T, err error) {
if !errors.Is(err, ErrAsyncPublishReplySubjectSet) {
t.Fatalf("Expected error: %v; got: %v", nats.ErrNoResponders, err)
}
},
},
},
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
var srv *server.Server
if test.srvConfig != nil {
conf := createConfFile(t, test.srvConfig)
defer os.Remove(conf)
srv, _ = RunServerWithConfig(conf)
} else {
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()
_, err = js.CreateStream(ctx, StreamConfig{Name: "foo", Subjects: []string{"FOO.*"}, MaxMsgSize: 64})
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
for _, pub := range test.msgs {
ackFuture, err := js.PublishMsgAsync(ctx, pub.msg, pub.opts...)
if pub.withPublishError != nil {
pub.withPublishError(t, err)
continue
}
select {
case ack := <-ackFuture.Ok():
if pub.withAckError != nil {
t.Fatalf("Expected error, got nil")
}
if *ack != pub.expectedAck {
t.Fatalf("Invalid ack received; want: %v; got: %v", pub.expectedAck, ack)
}
msg := ackFuture.Msg()
if !reflect.DeepEqual(pub.expectedHeaders, msg.Header) {
t.Fatalf("Invalid headers on message; want: %v; got: %v", pub.expectedHeaders, pub.msg.Header)
}
if string(msg.Data) != string(pub.msg.Data) {
t.Fatalf("Invaid message in ack; want: %q; got: %q", string(pub.msg.Data), string(msg.Data))
}
case err := <-ackFuture.Err():
if pub.withAckError == nil {
t.Fatalf("Expected no error. got: %v", err)
}
pub.withAckError(t, err)
}
}
select {
case <-js.PublishAsyncComplete():
case <-time.After(5 * time.Second):
t.Fatalf("Did not receive completion signal")
}
})
}
}
func TestPublishMsgAsyncWithPendingMsgs(t *testing.T) {
t.Run("outstanding ack exceed limit", 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, WithPublishAsyncMaxPending(5))
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
defer nc.Close()
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
_, err = js.CreateStream(ctx, StreamConfig{Name: "foo", Subjects: []string{"FOO.*"}})
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
for i := 0; i < 20; i++ {
_, err = js.PublishAsync(ctx, "FOO.1", []byte("msg"))
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
if numPending := js.PublishAsyncPending(); numPending > 5 {
t.Fatalf("Expected 5 pending messages, got: %d", numPending)
}
}
})
t.Run("too many messages without ack", 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, WithPublishAsyncMaxPending(5))
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
defer nc.Close()
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
_, 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++ {
_, err = js.PublishAsync(ctx, "FOO.1", []byte("msg"), WithStallWait(1*time.Nanosecond))
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
}
if _, err = js.PublishAsync(ctx, "FOO.1", []byte("msg"), WithStallWait(1*time.Nanosecond)); err == nil || !errors.Is(err, ErrTooManyStalledMsgs) {
t.Fatalf("Expected error: %v; got: %v", ErrTooManyStalledMsgs, err)
}
})
}