Files

212 lines
6.3 KiB
Go

// Copyright 2022-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 test
import (
"context"
"errors"
"fmt"
"os"
"testing"
"time"
"github.com/nats-io/nats.go"
"github.com/nats-io/nats.go/jetstream"
)
func TestJetStreamErrors(t *testing.T) {
t.Run("API error", func(t *testing.T) {
conf := createConfFile(t, []byte(`
listen: 127.0.0.1:-1
no_auth_user: rip
jetstream: {max_mem_store: 64GB, max_file_store: 10TB}
accounts: {
JS: {
jetstream: enabled
users: [ {user: dlc, password: foo} ]
},
IU: {
users: [ {user: rip, password: bar} ]
},
}
`))
defer os.Remove(conf)
s, _ := RunServerWithConfig(conf)
defer shutdownJSServerAndRemoveStorage(t, s)
nc, err := nats.Connect(s.ClientURL())
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
js, err := jetstream.New(nc)
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
defer nc.Close()
_, err = js.AccountInfo(ctx)
// check directly to var (backwards compatible)
if err != jetstream.ErrJetStreamNotEnabledForAccount {
t.Fatalf("Did not get the proper error, got %v", err)
}
// matching via errors.Is
if ok := errors.Is(err, jetstream.ErrJetStreamNotEnabledForAccount); !ok {
t.Fatal("Expected jetstream.ErrJetStreamNotEnabledForAccount")
}
// matching wrapped via error.Is
err2 := fmt.Errorf("custom error: %w", jetstream.ErrJetStreamNotEnabledForAccount)
if ok := errors.Is(err2, jetstream.ErrJetStreamNotEnabledForAccount); !ok {
t.Fatal("Expected wrapped ErrJetStreamNotEnabled")
}
// via classic type assertion.
jserr, ok := err.(jetstream.JetStreamError)
if !ok {
t.Fatal("Expected a jetstream.JetStreamError")
}
expected := jetstream.JSErrCodeJetStreamNotEnabledForAccount
if jserr.APIError().ErrorCode != expected {
t.Fatalf("Expected: %v, got: %v", expected, jserr.APIError().ErrorCode)
}
if jserr.APIError() == nil {
t.Fatal("Expected APIError")
}
// matching to interface via errors.As(...)
var apierr jetstream.JetStreamError
ok = errors.As(err, &apierr)
if !ok {
t.Fatal("Expected a jetstream.JetStreamError")
}
if apierr.APIError() == nil {
t.Fatal("Expected APIError")
}
if apierr.APIError().ErrorCode != expected {
t.Fatalf("Expected: %v, got: %v", expected, apierr.APIError().ErrorCode)
}
expectedMessage := "nats: API error: code=503 err_code=10039 description=jetstream not enabled for account"
if apierr.Error() != expectedMessage {
t.Fatalf("Expected: %v, got: %v", expectedMessage, apierr.Error())
}
// an APIError also implements the jetstream.JetStreamError interface.
var _ jetstream.JetStreamError = &jetstream.APIError{}
// matching arbitrary custom error via errors.Is(...)
customErr := &jetstream.APIError{ErrorCode: expected}
if ok := errors.Is(customErr, jetstream.ErrJetStreamNotEnabledForAccount); !ok {
t.Fatal("Expected wrapped jetstream.ErrJetStreamNotEnabledForAccount")
}
customErr = &jetstream.APIError{ErrorCode: 1}
if ok := errors.Is(customErr, jetstream.ErrJetStreamNotEnabledForAccount); ok {
t.Fatal("Expected to not match ErrJetStreamNotEnabled")
}
var cerr jetstream.JetStreamError
if ok := errors.As(customErr, &cerr); !ok {
t.Fatal("Expected custom error to be a jetstream.JetStreamError")
}
// matching to concrete type via errors.As(...)
var aerr *jetstream.APIError
ok = errors.As(err, &aerr)
if !ok {
t.Fatal("Expected an APIError")
}
if aerr.ErrorCode != expected {
t.Fatalf("Expected: %v, got: %v", expected, aerr.ErrorCode)
}
expectedMessage = "nats: API error: code=503 err_code=10039 description=jetstream not enabled for account"
if aerr.Error() != expectedMessage {
t.Fatalf("Expected: %v, got: %v", expectedMessage, apierr.Error())
}
})
t.Run("test non-api error", func(t *testing.T) {
s := RunBasicJetStreamServer()
defer shutdownJSServerAndRemoveStorage(t, s)
nc, err := nats.Connect(s.ClientURL())
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
js, err := jetstream.New(nc)
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
defer nc.Close()
// stream with empty name
_, err = js.CreateStream(ctx, jetstream.StreamConfig{})
if err == nil {
t.Fatalf("Expected error, got nil")
}
// check directly to var (backwards compatible)
if err != jetstream.ErrStreamNameRequired {
t.Fatalf("Expected: %v; got: %v", jetstream.ErrStreamNameRequired, err)
}
// matching via errors.Is
if ok := errors.Is(err, jetstream.ErrStreamNameRequired); !ok {
t.Fatalf("Expected: %v; got: %v", jetstream.ErrStreamNameRequired, err)
}
// matching wrapped via error.Is
err2 := fmt.Errorf("custom error: %w", jetstream.ErrStreamNameRequired)
if ok := errors.Is(err2, jetstream.ErrStreamNameRequired); !ok {
t.Fatal("Expected wrapped ErrStreamNameRequired")
}
// via classic type assertion.
jserr, ok := err.(jetstream.JetStreamError)
if !ok {
t.Fatal("Expected a jetstream.JetStreamError")
}
if jserr.APIError() != nil {
t.Fatalf("Expected: empty APIError; got: %v", jserr.APIError())
}
// matching to interface via errors.As(...)
var jserr2 jetstream.JetStreamError
ok = errors.As(err, &jserr2)
if !ok {
t.Fatal("Expected a jetstream.JetStreamError")
}
if jserr2.APIError() != nil {
t.Fatalf("Expected: empty APIError; got: %v", jserr2.APIError())
}
expectedMessage := "nats: stream name is required"
if jserr2.Error() != expectedMessage {
t.Fatalf("Expected: %v, got: %v", expectedMessage, jserr2.Error())
}
// matching to concrete type via errors.As(...)
var aerr *jetstream.APIError
ok = errors.As(err, &aerr)
if ok {
t.Fatal("Expected ErrStreamNameRequired not to map to APIError")
}
})
}