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