mirror of
https://github.com/hanzoai/pubsub-go.git
synced 2026-10-30 18:23:30 +00:00
583 lines
15 KiB
Go
583 lines
15 KiB
Go
package test
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"sync"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/hanzoai/pubsub-go"
|
|
"github.com/hanzoai/pubsub-go/jetstream"
|
|
)
|
|
|
|
func TestPushConsumerConsume(t *testing.T) {
|
|
testSubject := "FOO.123"
|
|
testMsgs := []string{"m1", "m2", "m3", "m4", "m5"}
|
|
publishTestMsgs := func(t *testing.T, js jetstream.JetStream) {
|
|
for _, msg := range testMsgs {
|
|
if _, err := js.Publish(context.Background(), testSubject, []byte(msg)); err != nil {
|
|
t.Fatalf("Unexpected error during publish: %s", err)
|
|
}
|
|
}
|
|
}
|
|
|
|
t.Run("no options", func(t *testing.T) {
|
|
srv := RunBasicJetStreamServer()
|
|
defer shutdownJSServerAndRemoveStorage(t, srv)
|
|
nc, err := nats.Connect(srv.ClientURL())
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
|
|
js, err := jetstream.New(nc)
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
defer nc.Close()
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second)
|
|
defer cancel()
|
|
s, err := js.CreateStream(ctx, jetstream.StreamConfig{Name: "foo", Subjects: []string{"FOO.*"}})
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
c, err := s.CreatePushConsumer(ctx, jetstream.ConsumerConfig{
|
|
DeliverSubject: nats.NewInbox(),
|
|
AckPolicy: jetstream.AckExplicitPolicy,
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
|
|
msgs := make([]jetstream.Msg, 0)
|
|
wg := &sync.WaitGroup{}
|
|
wg.Add(len(testMsgs))
|
|
l, err := c.Consume(func(msg jetstream.Msg) {
|
|
msg.Ack()
|
|
msgs = append(msgs, msg)
|
|
wg.Done()
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
defer l.Stop()
|
|
|
|
publishTestMsgs(t, js)
|
|
wg.Wait()
|
|
if len(msgs) != len(testMsgs) {
|
|
t.Fatalf("Unexpected received message count; want %d; got %d", len(testMsgs), len(msgs))
|
|
}
|
|
for i, msg := range msgs {
|
|
if string(msg.Data()) != testMsgs[i] {
|
|
t.Fatalf("Invalid msg on index %d; expected: %s; got: %s", i, testMsgs[i], string(msg.Data()))
|
|
}
|
|
}
|
|
})
|
|
|
|
t.Run("consumer already consuming", 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 := jetstream.New(nc)
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
defer nc.Close()
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second)
|
|
defer cancel()
|
|
s, err := js.CreateStream(ctx, jetstream.StreamConfig{Name: "foo", Subjects: []string{"FOO.*"}})
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
c, err := s.CreatePushConsumer(ctx, jetstream.ConsumerConfig{
|
|
DeliverSubject: nats.NewInbox(),
|
|
AckPolicy: jetstream.AckExplicitPolicy,
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
|
|
l, err := c.Consume(func(msg jetstream.Msg) {
|
|
msg.Ack()
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
defer l.Stop()
|
|
|
|
_, err = c.Consume(func(msg jetstream.Msg) {})
|
|
if !errors.Is(err, jetstream.ErrConsumerAlreadyConsuming) {
|
|
t.Fatalf("Expected error; got none")
|
|
}
|
|
})
|
|
|
|
t.Run("missing heartbeats", 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 := jetstream.New(nc)
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
defer nc.Close()
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second)
|
|
defer cancel()
|
|
s, err := js.CreateStream(ctx, jetstream.StreamConfig{Name: "foo", Subjects: []string{"FOO.*"}})
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
c, err := s.CreatePushConsumer(ctx, jetstream.ConsumerConfig{
|
|
DeliverSubject: nats.NewInbox(),
|
|
AckPolicy: jetstream.AckExplicitPolicy,
|
|
IdleHeartbeat: 100 * time.Millisecond,
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
|
|
errs := make(chan error, 1)
|
|
l, err := c.Consume(func(msg jetstream.Msg) {},
|
|
jetstream.ConsumeErrHandler(func(cc jetstream.ConsumeContext, err error) {
|
|
errs <- err
|
|
}))
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
defer l.Stop()
|
|
time.Sleep(300 * time.Millisecond)
|
|
// delete consumer to simulate missing heartbeats
|
|
if err := s.DeleteConsumer(context.Background(), c.CachedInfo().Name); err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
|
|
select {
|
|
case <-time.After(2 * time.Second):
|
|
t.Fatalf("Expected error; got none")
|
|
case err := <-errs:
|
|
if !errors.Is(err, jetstream.ErrNoHeartbeat) {
|
|
t.Fatalf("Expected error: %v; got: %v", jetstream.ErrNoHeartbeat, err)
|
|
}
|
|
}
|
|
})
|
|
|
|
t.Run("resubscribe", 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 := jetstream.New(nc)
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
defer nc.Close()
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second)
|
|
defer cancel()
|
|
s, err := js.CreateStream(ctx, jetstream.StreamConfig{Name: "foo", Subjects: []string{"FOO.*"}})
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
c, err := s.CreatePushConsumer(ctx, jetstream.ConsumerConfig{
|
|
DeliverSubject: nats.NewInbox(),
|
|
AckPolicy: jetstream.AckExplicitPolicy,
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
|
|
msgs := make([]jetstream.Msg, 0)
|
|
wg := &sync.WaitGroup{}
|
|
publishTestMsgs(t, js)
|
|
wg.Add(len(testMsgs))
|
|
cc, err := c.Consume(func(msg jetstream.Msg) {
|
|
msgs = append(msgs, msg)
|
|
wg.Done()
|
|
msg.Ack()
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
wg.Wait()
|
|
if len(msgs) != len(testMsgs) {
|
|
t.Fatalf("Unexpected received message count; want %d; got %d", len(testMsgs), len(msgs))
|
|
}
|
|
cc.Stop()
|
|
publishTestMsgs(t, js)
|
|
wg.Add(len(testMsgs))
|
|
time.Sleep(100 * time.Millisecond)
|
|
if len(msgs) != len(testMsgs) {
|
|
t.Fatalf("Unexpected received message count; want %d; got %d", len(testMsgs), len(msgs))
|
|
}
|
|
|
|
cc, err = c.Consume(func(msg jetstream.Msg) {
|
|
msgs = append(msgs, msg)
|
|
wg.Done()
|
|
msg.Ack()
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
defer cc.Stop()
|
|
wg.Wait()
|
|
if len(msgs) != 2*len(testMsgs) {
|
|
t.Fatalf("Unexpected received message count; want %d; got %d", 2*len(testMsgs), len(msgs))
|
|
}
|
|
|
|
})
|
|
|
|
t.Run("with server restart", func(t *testing.T) {
|
|
srv := RunBasicJetStreamServer()
|
|
nc, err := nats.Connect(srv.ClientURL())
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
|
|
js, err := jetstream.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, jetstream.StreamConfig{Name: "foo", Subjects: []string{"FOO.*"}})
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
c, err := s.CreatePushConsumer(ctx, jetstream.ConsumerConfig{
|
|
DeliverSubject: nats.NewInbox(),
|
|
AckPolicy: jetstream.AckExplicitPolicy,
|
|
IdleHeartbeat: time.Second,
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
|
|
wg := &sync.WaitGroup{}
|
|
wg.Add(2 * len(testMsgs))
|
|
msgs := make([]jetstream.Msg, 0)
|
|
publishTestMsgs(t, js)
|
|
l, err := c.Consume(func(msg jetstream.Msg) {
|
|
msgs = append(msgs, msg)
|
|
wg.Done()
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
defer l.Stop()
|
|
time.Sleep(10 * time.Millisecond)
|
|
// restart the server
|
|
srv = restartBasicJSServer(t, srv)
|
|
defer shutdownJSServerAndRemoveStorage(t, srv)
|
|
time.Sleep(10 * time.Millisecond)
|
|
publishTestMsgs(t, js)
|
|
wg.Wait()
|
|
})
|
|
|
|
t.Run("drain", 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 := jetstream.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, jetstream.StreamConfig{Name: "foo", Subjects: []string{"FOO.*"}})
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
c, err := s.CreatePushConsumer(ctx, jetstream.ConsumerConfig{
|
|
AckPolicy: jetstream.AckExplicitPolicy,
|
|
DeliverSubject: nats.NewInbox(),
|
|
IdleHeartbeat: time.Second,
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
wg := &sync.WaitGroup{}
|
|
wg.Add(5)
|
|
publishTestMsgs(t, js)
|
|
cc, err := c.Consume(func(msg jetstream.Msg) {
|
|
time.Sleep(50 * time.Millisecond)
|
|
msg.Ack()
|
|
wg.Done()
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
time.Sleep(100 * time.Millisecond)
|
|
cc.Drain()
|
|
wg.Wait()
|
|
})
|
|
|
|
t.Run("wait for closed after drain", 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 := jetstream.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, jetstream.StreamConfig{Name: "foo", Subjects: []string{"FOO.*"}})
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
c, err := s.CreatePushConsumer(ctx, jetstream.ConsumerConfig{
|
|
AckPolicy: jetstream.AckExplicitPolicy,
|
|
DeliverSubject: nats.NewInbox(),
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
msgs := make([]jetstream.Msg, 0)
|
|
lock := sync.Mutex{}
|
|
publishTestMsgs(t, js)
|
|
cc, err := c.Consume(func(msg jetstream.Msg) {
|
|
time.Sleep(50 * time.Millisecond)
|
|
msg.Ack()
|
|
lock.Lock()
|
|
msgs = append(msgs, msg)
|
|
lock.Unlock()
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
closed := cc.Closed()
|
|
time.Sleep(100 * time.Millisecond)
|
|
|
|
cc.Drain()
|
|
|
|
select {
|
|
case <-closed:
|
|
case <-time.After(5 * time.Second):
|
|
t.Fatalf("Timeout waiting for consume to be closed")
|
|
}
|
|
|
|
if len(msgs) != len(testMsgs) {
|
|
t.Fatalf("Unexpected received message count after consume closed; want %d; got %d", len(testMsgs), len(msgs))
|
|
}
|
|
})
|
|
|
|
t.Run("wait for closed on already closed consume", 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 := jetstream.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, jetstream.StreamConfig{Name: "foo", Subjects: []string{"FOO.*"}})
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
c, err := s.CreatePushConsumer(ctx, jetstream.ConsumerConfig{
|
|
AckPolicy: jetstream.AckExplicitPolicy,
|
|
DeliverSubject: nats.NewInbox(),
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
publishTestMsgs(t, js)
|
|
cc, err := c.Consume(func(msg jetstream.Msg) {
|
|
time.Sleep(50 * time.Millisecond)
|
|
msg.Ack()
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
time.Sleep(100 * time.Millisecond)
|
|
|
|
cc.Stop()
|
|
|
|
time.Sleep(100 * time.Millisecond)
|
|
|
|
select {
|
|
case <-cc.Closed():
|
|
case <-time.After(5 * time.Second):
|
|
t.Fatalf("Timeout waiting for consume to be closed")
|
|
}
|
|
})
|
|
|
|
t.Run("empty handler", 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 := jetstream.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, jetstream.StreamConfig{Name: "foo", Subjects: []string{"FOO.*"}})
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
c, err := s.CreatePushConsumer(ctx, jetstream.ConsumerConfig{
|
|
AckPolicy: jetstream.AckExplicitPolicy,
|
|
DeliverSubject: nats.NewInbox(),
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
_, err = c.Consume(nil)
|
|
if !errors.Is(err, jetstream.ErrHandlerRequired) {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
})
|
|
|
|
t.Run("stop and drain idempotent", 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 := jetstream.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, jetstream.StreamConfig{Name: "foo", Subjects: []string{"FOO.*"}})
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
c, err := s.CreatePushConsumer(ctx, jetstream.ConsumerConfig{
|
|
AckPolicy: jetstream.AckExplicitPolicy,
|
|
DeliverSubject: nats.NewInbox(),
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
cc, err := c.Consume(func(msg jetstream.Msg) {
|
|
time.Sleep(50 * time.Millisecond)
|
|
msg.Ack()
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
cc.Stop()
|
|
cc.Stop()
|
|
cc.Drain()
|
|
cc.Drain()
|
|
})
|
|
}
|
|
|
|
func TestPushConsumerConsume_WithQueue(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 := jetstream.New(nc)
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
defer nc.Close()
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second)
|
|
defer cancel()
|
|
s, err := js.CreateStream(ctx, jetstream.StreamConfig{Name: "foo", Subjects: []string{"FOO.*"}})
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
c1, err := s.CreatePushConsumer(ctx, jetstream.ConsumerConfig{
|
|
DeliverSubject: nats.NewInbox(),
|
|
DeliverGroup: "workers",
|
|
AckPolicy: jetstream.AckExplicitPolicy,
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
|
|
c2, err := s.PushConsumer(ctx, c1.CachedInfo().Name)
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
|
|
msgs := make([]jetstream.Msg, 0)
|
|
lock := sync.Mutex{}
|
|
wg := &sync.WaitGroup{}
|
|
l1, err := c1.Consume(func(msg jetstream.Msg) {
|
|
msg.Ack()
|
|
lock.Lock()
|
|
msgs = append(msgs, msg)
|
|
lock.Unlock()
|
|
wg.Done()
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
defer l1.Stop()
|
|
l2, err := c2.Consume(func(msg jetstream.Msg) {
|
|
msg.Ack()
|
|
lock.Lock()
|
|
msgs = append(msgs, msg)
|
|
lock.Unlock()
|
|
wg.Done()
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
defer l2.Stop()
|
|
|
|
testSubject := "FOO.123"
|
|
testMsgs := []string{"m1", "m2", "m3", "m4", "m5"}
|
|
wg.Add(len(testMsgs))
|
|
for _, msg := range testMsgs {
|
|
if _, err := js.Publish(context.Background(), testSubject, []byte(msg)); err != nil {
|
|
t.Fatalf("Unexpected error during publish: %s", err)
|
|
}
|
|
}
|
|
|
|
wg.Wait()
|
|
if len(msgs) != len(testMsgs) {
|
|
t.Fatalf("Unexpected received message count; want %d; got %d", len(testMsgs), len(msgs))
|
|
}
|
|
}
|