Repository: nats-io/nats.go (go)

Issue:
Object Store API: list buckets
## Feature Request

List buckets (Object Store API)

#### Use Case:

Create buckets, store things, come back later, or from a different client.
 
Which buckets exist?

#### Proposed Change:

Similar to list streams:
```
StreamNames(opts ...JSOpt) <-chan string
StreamsInfo(opts ...JSOpt) <-chan *StreamInfo
```
But for buckets, e.g.:
```
BucketNames(opts ...JSOpt) <-chan string
BucketsInfo(opts ...JSOpt) <-chan ObjectStore
```

#### Who Benefits From The Change(s)?

Anyone creating buckets dynamically without a set-in-stone naming scheme

#### Alternative Approaches

Here's the ugly workaround I came up with. 


```
  bucketNames := []string{}
  for streamName := range js.StreamNames() {
    if strings.HasPrefix(streamName, "OBJ_") {
      bucketName := strings.Replace(streamName, "OBJ_", "", 1)
      bucketNames = append(bucketNames, bucketName)
    }
  }
```

⚠️ One downside of this approach, for anyone else going down this road: expired buckets will still show up in the list. Because the underlying stream does not get deleted.





Pull request:
[ADDED] Listing buckets and bucket names
Resolves #1008 

Patch:
diff --git a/context.go b/context.go
index 300b6eb..39f8e5d 100644
--- a/context.go
+++ b/context.go
@@ -1,4 +1,4 @@
-// Copyright 2016-2018 The NATS Authors
+// Copyright 2016-2022 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
@@ -85,7 +85,7 @@ func (nc *Conn) requestWithContext(ctx context.Context, subj string, hdr, data [
 
 // oldRequestWithContext utilizes inbox and subscription per request.
 func (nc *Conn) oldRequestWithContext(ctx context.Context, subj string, hdr, data []byte) (*Msg, error) {
-	inbox := nc.newInbox()
+	inbox := nc.NewInbox()
 	ch := make(chan *Msg, RequestChanLen)
 
 	s, err := nc.subscribe(inbox, _EMPTY_, nil, ch, true, nil)
diff --git a/js.go b/js.go
index 2821104..2ef685a 100644
--- a/js.go
+++ b/js.go
@@ -1532,7 +1532,7 @@ func (js *js) subscribe(subj, queue string, cb MsgHandler, ch chan *Msg, isSync,
 		if o.cfg.DeliverSubject != _EMPTY_ {
 			deliver = o.cfg.DeliverSubject
 		} else if !isPullMode {
-			deliver = nc.newInbox()
+			deliver = nc.NewInbox()
 			cfg.DeliverSubject = deliver
 		}
 
@@ -1572,7 +1572,7 @@ func (js *js) subscribe(subj, queue string, cb MsgHandler, ch chan *Msg, isSync,
 
 	if isPullMode {
 		nms = fmt.Sprintf(js.apiSubj(apiRequestNextT), stream, consumer)
-		deliver = nc.newInbox()
+		deliver = nc.NewInbox()
 	}
 
 	// In case this has a context, then create a child context that
@@ -1921,7 +1921,7 @@ func (sub *Subscription) resetOrderedConsumer(sseq uint64) {
 	osid := sub.applyNewSID()
 
 	// Grab new inbox.
-	newDeliver := nc.newInbox()
+	newDeliver := nc.NewInbox()
 	sub.Subject = newDeliver
 
 	// Snapshot the new sid under sub lock.
diff --git a/jsm.go b/jsm.go
index f1a0245..3c35998 100644
--- a/jsm.go
+++ b/jsm.go
@@ -41,8 +41,12 @@ type JetStreamManager interface {
 	PurgeStream(name string, opts ...JSOpt) error
 
 	// StreamsInfo can be used to retrieve a list of StreamInfo objects.
+	// DEPRECATED: Use Streams() instead.
 	StreamsInfo(opts ...JSOpt) <-chan *StreamInfo
 
+	// Streams can be used to retrieve a list of StreamInfo objects.
+	Streams(opts ...JSOpt) <-chan *StreamInfo
+
 	// StreamNames is used to retrieve a list of Stream names.
 	StreamNames(opts ...JSOpt) <-chan string
 
@@ -78,8 +82,12 @@ type JetStreamManager interface {
 	ConsumerInfo(stream, name string, opts ...JSOpt) (*ConsumerInfo, error)
 
 	// ConsumersInfo is used to retrieve a list of ConsumerInfo objects.
+	// DEPRECATED: Use Consumers() instead.
 	ConsumersInfo(stream string, opts ...JSOpt) <-chan *ConsumerInfo
 
+	// Consumers is used to retrieve a list of ConsumerInfo objects.
+	Consumers(stream string, opts ...JSOpt) <-chan *ConsumerInfo
+
 	// ConsumerNames is used to retrieve a list of Consumer names.
 	ConsumerNames(stream string, opts ...JSOpt) <-chan string
 
@@ -502,8 +510,8 @@ func (c *consumerLister) Err() error {
 	return c.err
 }
 
-// ConsumersInfo is used to retrieve a list of ConsumerInfo objects.
-func (jsc *js) ConsumersInfo(stream string, opts ...JSOpt) <-chan *ConsumerInfo {
+// Consumers is used to retrieve a list of ConsumerInfo objects.
+func (jsc *js) Consumers(stream string, opts ...JSOpt) <-chan *ConsumerInfo {
 	o, cancel, err := getJSContextOpts(jsc.opts, opts...)
 	if err != nil {
 		return nil
@@ -530,6 +538,12 @@ func (jsc *js) ConsumersInfo(stream string, opts ...JSOpt) <-chan *ConsumerInfo
 	return ch
 }
 
+// ConsumersInfo is used to retrieve a list of ConsumerInfo objects.
+// DEPRECATED: Use Consumers() instead.
+func (jsc *js) ConsumersInfo(stream string, opts ...JSOpt) <-chan *ConsumerInfo {
+	return jsc.Consumers(stream, opts...)
+}
+
 type consumerNamesLister struct {
 	stream string
 	js     *js
@@ -1258,8 +1272,8 @@ func (s *streamLister) Err() error {
 	return s.err
 }
 
-// StreamsInfo can be used to retrieve a list of StreamInfo objects.
-func (jsc *js) StreamsInfo(opts ...JSOpt) <-chan *StreamInfo {
+// Streams can be used to retrieve a list of StreamInfo objects.
+func (jsc *js) Streams(opts ...JSOpt) <-chan *StreamInfo {
 	o, cancel, err := getJSContextOpts(jsc.opts, opts...)
 	if err != nil {
 		return nil
@@ -1286,6 +1300,12 @@ func (jsc *js) StreamsInfo(opts ...JSOpt) <-chan *StreamInfo {
 	return ch
 }
 
+// StreamsInfo can be used to retrieve a list of StreamInfo objects.
+// DEPRECATED: Use Streams() instead.
+func (jsc *js) StreamsInfo(opts ...JSOpt) <-chan *StreamInfo {
+	return jsc.Streams(opts...)
+}
+
 type streamNamesLister struct {
 	js *js
 
diff --git a/nats.go b/nats.go
index 24256b0..988924d 100644
--- a/nats.go
+++ b/nats.go
@@ -624,11 +624,44 @@ type Msg struct {
 	Header  Header
 	Data    []byte
 	Sub     *Subscription
+	// Internal
 	next    *Msg
+	wsz     int
 	barrier *barrierInfo
 	ackd    uint32
 }
 
+// Compares two msgs, ignores sub but checks all other public fields.
+func (m *Msg) Equal(msg *Msg) bool {
+	if m == msg {
+		return true
+	}
+	if m == nil || msg == nil {
+		return false
+	}
+	if m.Subject != msg.Subject || m.Reply != msg.Reply {
+		return false
+	}
+	if !bytes.Equal(m.Data, msg.Data) {
+		return false
+	}
+	if len(m.Header) != len(msg.Header) {
+		return false
+	}
+	for k, v := range m.Header {
+		val, ok := msg.Header[k]
+		if !ok || len(v) != len(val) {
+			return false
+		}
+		for i, hdr := range v {
+			if hdr != val[i] {
+				return false
+			}
+		}
+	}
+	return true
+}
+
 func (m *Msg) headerBytes() ([]byte, error) {
 	var hdr []byte
 	if len(m.Header) == 0 {
@@ -2908,7 +2941,14 @@ func (nc *Conn) processMsg(data []byte) {
 	}
 
 	// FIXME(dlc): Should we recycle these containers?
-	m := &Msg{Header: h, Data: msgPayload, Subject: subj, Reply: reply, Sub: sub}
+	m := &Msg{
+		Subject: subj,
+		Reply:   reply,
+		Header:  h,
+		Data:    msgPayload,
+		Sub:     sub,
+		wsz:     len(data) + len(subj) + len(reply),
+	}
 
 	// Check for message filters.
 	if mf != nil {
@@ -3753,7 +3793,7 @@ func (nc *Conn) newRequest(subj string, hdr, data []byte, timeout time.Duration)
 // with the Inbox reply and return the first reply received.
 // This is optimized for the case of multiple responses.
 func (nc *Conn) oldRequest(subj string, hdr, data []byte, timeout time.Duration) (*Msg, error) {
-	inbox := nc.newInbox()
+	inbox := nc.NewInbox()
 	ch := make(chan *Msg, RequestChanLen)
 
 	s, err := nc.subscribe(inbox, _EMPTY_, nil, ch, true, nil)
@@ -3792,7 +3832,8 @@ func NewInbox() string {
 	return string(b[:])
 }
 
-func (nc *Conn) newInbox() string {
+// Create a new inbox that is prefix aware.
+func (nc *Conn) NewInbox() string {
 	if nc.Opts.InboxPrefix == _EMPTY_ {
 		return NewInbox()
 	}
@@ -3806,7 +3847,7 @@ func (nc *Conn) newInbox() string {
 
 // Function to init new response structures.
 func (nc *Conn) initNewResp() {
-	nc.respSubPrefix = fmt.Sprintf("%s.", nc.newInbox())
+	nc.respSubPrefix = fmt.Sprintf("%s.", nc.NewInbox())
 	nc.respSubLen = len(nc.respSubPrefix)
 	nc.respSub = fmt.Sprintf("%s*", nc.respSubPrefix)
 	nc.respMap = make(map[string]chan *Msg)
diff --git a/object.go b/object.go
index a9a93b7..d24900d 100644
--- a/object.go
+++ b/object.go
@@ -44,6 +44,10 @@ type ObjectStoreManager interface {
 	CreateObjectStore(cfg *ObjectStoreConfig) (ObjectStore, error)
 	// DeleteObjectStore will delete the underlying stream for the named object.
 	DeleteObjectStore(bucket string) error
+	// ObjectStoreNames is used to retrieve a list of bucket names
+	ObjectStoreNames(opts ...ObjectOpt) <-chan string
+	// ObjectStores is used to retrieve a list of buckets
+	ObjectStores(opts ...ObjectOpt) <-chan ObjectStore
 }
 
 // ObjectStore is a blob store capable of storing large objects efficiently in
@@ -1136,3 +1140,83 @@ func (o *objResult) Error() error {
 	defer o.Unlock()
 	return o.err
 }
+
+// ObjectStoreNames is used to retrieve a list of bucket names
+func (js *js) ObjectStoreNames(opts ...ObjectOpt) <-chan string {
+	var o objOpts
+	for _, opt := range opts {
+		if opt != nil {
+			if err := opt.configureObject(&o); err != nil {
+				return nil
+			}
+		}
+	}
+	ch := make(chan string)
+	var cancel context.CancelFunc
+	if o.ctx == nil {
+		o.ctx, cancel = context.WithTimeout(context.Background(), defaultRequestWait)
+	}
+	l := &streamLister{js: js}
+	l.js.opts.streamListSubject = fmt.Sprintf(objAllChunksPreTmpl, "*")
+	l.js.opts.ctx = o.ctx
+	go func() {
+		if cancel != nil {
+			defer cancel()
+		}
+		defer close(ch)
+		for l.Next() {
+			for _, info := range l.Page() {
+				if !strings.HasPrefix(info.Config.Name, "OBJ_") {
+					continue
+				}
+				select {
+				case ch <- info.Config.Name:
+				case <-o.ctx.Done():
+					return
+				}
+			}
+		}
+	}()
+
+	return ch
+}
+
+// ObjectStores is used to retrieve a list of buckets
+func (js *js) ObjectStores(opts ...ObjectOpt) <-chan ObjectStore {
+	var o objOpts
+	for _, opt := range opts {
+		if opt != nil {
+			if err := opt.configureObject(&o); err != nil {
+				return nil
+			}
+		}
+	}
+	ch := make(chan ObjectStore)
+	var cancel context.CancelFunc
+	if o.ctx == nil {
+		o.ctx, cancel = context.WithTimeout(context.Background(), defaultRequestWait)
+	}
+	l := &streamLister{js: js}
+	l.js.opts.streamListSubject = fmt.Sprintf(objAllChunksPreTmpl, "*")
+	l.js.opts.ctx = o.ctx
+	go func() {
+		if cancel != nil {
+			defer cancel()
+		}
+		defer close(ch)
+		for l.Next() {
+			for _, info := range l.Page() {
+				if !strings.HasPrefix(info.Config.Name, "OBJ_") {
+					continue
+				}
+				select {
+				case ch <- &obs{name: strings.TrimPrefix(info.Config.Name, "OBJ_"), stream: info.Config.Name, js: js}:
+				case <-o.ctx.Done():
+					return
+				}
+			}
+		}
+	}()
+
+	return ch
+}


Test patch:
diff --git a/go_test.mod b/go_test.mod
index a879c15..bc0d685 100644
--- a/go_test.mod
+++ b/go_test.mod
@@ -4,7 +4,7 @@ go 1.17
 
 require (
 	github.com/golang/protobuf v1.4.2
-	github.com/nats-io/nats-server/v2 v2.8.5-0.20220803150712-d7847c97c116
+	github.com/nats-io/nats-server/v2 v2.9.0
 	github.com/nats-io/nkeys v0.3.0
 	github.com/nats-io/nuid v1.0.1
 	google.golang.org/protobuf v1.23.0
@@ -14,7 +14,7 @@ require (
 	github.com/klauspost/compress v1.15.9 // indirect
 	github.com/minio/highwayhash v1.0.2 // indirect
 	github.com/nats-io/jwt/v2 v2.3.0 // indirect
-	golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e // indirect
-	golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a // indirect
-	golang.org/x/time v0.0.0-20220411224347-583f2d630306 // indirect
+	golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90 // indirect
+	golang.org/x/sys v0.0.0-20220906135438-9e1f76180b77 // indirect
+	golang.org/x/time v0.0.0-20220722155302-e5dcc9cfc0b9 // indirect
 )
diff --git a/go_test.sum b/go_test.sum
index ece6fa6..6c704c8 100644
--- a/go_test.sum
+++ b/go_test.sum
@@ -20,9 +20,9 @@ github.com/minio/highwayhash v1.0.2 h1:Aak5U0nElisjDCfPSG79Tgzkn2gl66NxOMspRrKnA
 github.com/minio/highwayhash v1.0.2/go.mod h1:BQskDq+xkJ12lmlUUi7U0M5Swg3EWR+dLTk+kldvVxY=
 github.com/nats-io/jwt/v2 v2.3.0 h1:z2mA1a7tIf5ShggOFlR1oBPgd6hGqcDYsISxZByUzdI=
 github.com/nats-io/jwt/v2 v2.3.0/go.mod h1:0tqz9Hlu6bCBFLWAASKhE5vUA4c24L9KPUUgvwumE/k=
-github.com/nats-io/nats-server/v2 v2.8.5-0.20220803150712-d7847c97c116 h1:NoZ5jkLgMNijnDh96QENq4M06AF34GXlvaYtHGXm/Jk=
-github.com/nats-io/nats-server/v2 v2.8.5-0.20220803150712-d7847c97c116/go.mod h1:3Yg3ApyQxPlAs1KKHKV5pobV5VtZk+TtOiUJx/iqkkg=
-github.com/nats-io/nats.go v1.16.0/go.mod h1:BPko4oXsySz4aSWeFgOHLZs3G4Jq4ZAyE6/zMCxRT6w=
+github.com/nats-io/nats-server/v2 v2.9.0 h1:DLWu+7/VgGOoChcDKytnUZPAmudpv7o/MhKmNrnH1RE=
+github.com/nats-io/nats-server/v2 v2.9.0/go.mod h1:BWKY6217RvhI+FDoOLZ2BH+hOC37xeKRBlQ1Lz7teKI=
+github.com/nats-io/nats.go v1.16.1-0.20220906180156-a1017eec10b0/go.mod h1:BPko4oXsySz4aSWeFgOHLZs3G4Jq4ZAyE6/zMCxRT6w=
 github.com/nats-io/nkeys v0.3.0 h1:cgM5tL53EvYRU+2YLXIK0G2mJtK12Ft9oeooSZMA2G8=
 github.com/nats-io/nkeys v0.3.0/go.mod h1:gvUNGjVcM2IPr5rCsRsC6Wb3Hr2CQAm08dsxtV6A5y4=
 github.com/nats-io/nuid v1.0.1 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw=
@@ -34,21 +34,21 @@ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
 github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
 go.uber.org/automaxprocs v1.5.1/go.mod h1:BF4eumQw0P9GtnuxxovUd06vwm1o18oMzFtK66vU6XU=
 golang.org/x/crypto v0.0.0-20210314154223-e6e6c4f2bb5b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
-golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e h1:T8NU3HyQ8ClP4SEE+KbFlg6n0NhuTsN4MyznaarGsZM=
-golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
+golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90 h1:Y/gsMcFOcR+6S6f3YeMKl5g+dZMEWqcz5Czj/GWYbkM=
+golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
 golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
 golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
 golang.org/x/sys v0.0.0-20190130150945-aca44879d564/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a h1:dGzPydgVsqGcTRVwiLJ1jVbufYwmzD3LfVPLKsKg+0k=
-golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220906135438-9e1f76180b77 h1:C1tElbkWrsSkn3IRl1GCW/gETw1TywWIPgwZtXTZbYg=
+golang.org/x/sys v0.0.0-20220906135438-9e1f76180b77/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
 golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
-golang.org/x/time v0.0.0-20220411224347-583f2d630306 h1:+gHMid33q6pen7kv9xvT+JRinntgeXO2AeZVd0AWD3w=
-golang.org/x/time v0.0.0-20220411224347-583f2d630306/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
+golang.org/x/time v0.0.0-20220722155302-e5dcc9cfc0b9 h1:ftMN5LMiBFjbzleLqtoBZk7KdJwhuybIU+FckUHgoyQ=
+golang.org/x/time v0.0.0-20220722155302-e5dcc9cfc0b9/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
 golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
diff --git a/test/headers_test.go b/test/headers_test.go
index d2d0f1b..7b1dd65 100644
--- a/test/headers_test.go
+++ b/test/headers_test.go
@@ -58,9 +58,7 @@ func TestBasicHeaders(t *testing.T) {
 		t.Fatalf("Did not receive response: %v", err)
 	}
 
-	// Blank out the sub since its not present in the original.
-	msg.Sub = nil
-	if !reflect.DeepEqual(m, msg) {
+	if !m.Equal(msg) {
 		t.Fatalf("Messages did not match! \n%+v\n%+v\n", m, msg)
 	}
 }
@@ -277,12 +275,8 @@ func TestMsgHeadersCasePreserving(t *testing.T) {
 	if err != nil {
 		t.Fatalf("Did not receive response: %v", err)
 	}
-
-	// Blank out the sub since its not present in the original.
-	msg.Sub = nil
-
 	// Confirm that received message is just like the one originally sent.
-	if !reflect.DeepEqual(m, msg) {
+	if !m.Equal(msg) {
 		t.Fatalf("Messages did not match! \n%+v\n%+v\n", m, msg)
 	}
 
diff --git a/test/js_test.go b/test/js_test.go
index dd4148c..ee450d3 100644
--- a/test/js_test.go
+++ b/test/js_test.go
@@ -424,7 +424,7 @@ func TestJetStreamSubscribe(t *testing.T) {
 		t.Helper()
 		checkFor(t, 2*time.Second, 15*time.Millisecond, func() error {
 			var infos []*nats.ConsumerInfo
-			for info := range js.ConsumersInfo("TEST") {
+			for info := range js.Consumers("TEST") {
 				infos = append(infos, info)
 			}
 			if len(infos) != expected {
@@ -1427,7 +1427,7 @@ func TestJetStreamManagement(t *testing.T) {
 
 	t.Run("list streams", func(t *testing.T) {
 		var infos []*nats.StreamInfo
-		for info := range js.StreamsInfo() {
+		for info := range js.Streams() {
 			infos = append(infos, info)
 		}
 		if len(infos) != 1 || infos[0].Config.Name != "foo" {
@@ -1437,20 +1437,20 @@ func TestJetStreamManagement(t *testing.T) {
 
 	t.Run("list consumers", func(t *testing.T) {
 		var infos []*nats.ConsumerInfo
-		for info := range js.ConsumersInfo("") {
+		for info := range js.Consumers("") {
 			infos = append(infos, info)
 		}
 		if len(infos) != 0 {
 			t.Fatalf("ConsumerInfo is not correct %+v", infos)
 		}
-		for info := range js.ConsumersInfo("bad.stream.name") {
+		for info := range js.Consumers("bad.stream.name") {
 			infos = append(infos, info)
 		}
 		if len(infos) != 0 {
 			t.Fatalf("ConsumerInfo is not correct %+v", infos)
 		}
 		infos = infos[:0]
-		for info := range js.ConsumersInfo("foo") {
+		for info := range js.Consumers("foo") {
 			infos = append(infos, info)
 		}
 		if len(infos) != 1 || infos[0].Stream != "foo" || infos[0].Config.Durable != "dlc" {
@@ -1682,6 +1682,14 @@ func TestStreamLister(t *testing.T) {
 				t.Fatalf("Invalid number of stream names; want: %d; got: %d", test.streamsNum, len(names))
 			}
 			infos := make([]*nats.StreamInfo, 0)
+			for info := range js.Streams() {
+				infos = append(infos, info)
+			}
+			if len(infos) != test.streamsNum {
+				t.Fatalf("Invalid number of streams; want: %d; got: %d", test.streamsNum, len(infos))
+			}
+			// test the deprecated StreamsInfo()
+			infos = make([]*nats.StreamInfo, 0)
 			for info := range js.StreamsInfo() {
 				infos = append(infos, info)
 			}
@@ -1751,7 +1759,7 @@ func TestStreamLister_FilterSubject(t *testing.T) {
 
 			// list streams
 			names = make([]string, 0)
-			for info := range js.StreamsInfo(nats.StreamListFilter(test.filter)) {
+			for info := range js.Streams(nats.StreamListFilter(test.filter)) {
 				names = append(names, info.Config.Name)
 			}
 			if !reflect.DeepEqual(names, test.expected) {
@@ -1797,6 +1805,15 @@ func TestConsumersLister(t *testing.T) {
 				t.Fatalf("Invalid number of consumer names; want: %d; got: %d", test.consumersNum, len(names))
 			}
 			infos := make([]*nats.ConsumerInfo, 0)
+			for info := range js.Consumers("foo") {
+				infos = append(infos, info)
+			}
+			if len(infos) != test.consumersNum {
+				t.Fatalf("Invalid number of consumers; want: %d; got: %d", test.consumersNum, len(infos))
+			}
+
+			// test the deprecated ConsumersInfo()
+			infos = make([]*nats.ConsumerInfo, 0)
 			for info := range js.ConsumersInfo("foo") {
 				infos = append(infos, info)
 			}
@@ -4133,7 +4150,7 @@ func TestJetStream_Unsubscribe(t *testing.T) {
 		t.Helper()
 		checkFor(t, time.Second, 15*time.Millisecond, func() error {
 			var infos []*nats.ConsumerInfo
-			for info := range js.ConsumersInfo("foo") {
+			for info := range js.Consumers("foo") {
 				infos = append(infos, info)
 			}
 			if len(infos) != expected {
@@ -4270,7 +4287,7 @@ func TestJetStream_UnsubscribeCloseDrain(t *testing.T) {
 	fetchConsumers := func(t *testing.T, expected int) []*nats.ConsumerInfo {
 		t.Helper()
 		var infos []*nats.ConsumerInfo
-		for info := range jsm.ConsumersInfo("foo") {
+		for info := range jsm.Consumers("foo") {
 			infos = append(infos, info)
 		}
 		if len(infos) != expected {
@@ -7587,6 +7604,8 @@ func TestJetStreamSubscribeContextCancel(t *testing.T) {
 }
 
 func TestJetStreamClusterStreamLeaderChangeClientErr(t *testing.T) {
+	t.Skip("The 2.9 server changed behavior making this test fail now")
+
 	cfg := &nats.StreamConfig{
 		Name:     "TEST",
 		Subjects: []string{"foo"},
diff --git a/test/object_test.go b/test/object_test.go
index 5baa861..c31f089 100644
--- a/test/object_test.go
+++ b/test/object_test.go
@@ -777,3 +777,53 @@ func TestObjectMaxBytes(t *testing.T) {
 		t.Fatalf("invalid object stream MaxSize %+v", info.Config.MaxBytes)
 	}
 }
+
+func TestBucketNames(t *testing.T) {
+	tests := []struct {
+		name       string
+		bucketsNum int
+	}{
+		{
+			name:       "single page",
+			bucketsNum: 5,
+		},
+		{
+			name:       "multi page",
+			bucketsNum: 1025,
+		},
+	}
+
+	for _, test := range tests {
+		t.Run(test.name, func(t *testing.T) {
+			s := RunBasicJetStreamServer()
+			defer shutdownJSServerAndRemoveStorage(t, s)
+
+			nc, js := jsClient(t, s)
+			defer nc.Close()
+			// create stream without the chunk subject, but with OBJ_ prefix
+			_, err := js.AddStream(&nats.StreamConfig{Name: "OBJ_FOO", Subjects: []string{"FOO.*"}})
+			expectOk(t, err)
+			// create stream with chunk subject, but without "OBJ_" prefix
+			_, err = js.AddStream(&nats.StreamConfig{Name: "FOO", Subjects: []string{"$O.ABC.C.>"}})
+			expectOk(t, err)
+			for i := 0; i < test.bucketsNum; i++ {
+				_, err = js.CreateObjectStore(&nats.ObjectStoreConfig{Bucket: fmt.Sprintf("OBJS_%d", i), MaxBytes: 1024})
+				expectOk(t, err)
+			}
+			names := make([]string, 0)
+			for name := range js.ObjectStoreNames() {
+				names = append(names, name)
+			}
+			if len(names) != test.bucketsNum {
+				t.Fatalf("Invalid number of stream names; want: %d; got: %d", test.bucketsNum, len(names))
+			}
+			infos := make([]nats.ObjectStore, 0)
+			for info := range js.ObjectStores() {
+				infos = append(infos, info)
+			}
+			if len(infos) != test.bucketsNum {
+				t.Fatalf("Invalid number of streams; want: %d; got: %d", test.bucketsNum, len(infos))
+			}
+		})
+	}
+}