mirror of https://github.com/hashicorp/boundary
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
524 lines
13 KiB
524 lines
13 KiB
package errors_test
|
|
|
|
import (
|
|
"context"
|
|
stderrors "errors"
|
|
"fmt"
|
|
"testing"
|
|
|
|
"github.com/hashicorp/boundary/internal/db"
|
|
"github.com/hashicorp/boundary/internal/errors"
|
|
"github.com/jackc/pgconn"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
func Test_ErrorE(t *testing.T) {
|
|
t.Parallel()
|
|
errRecordNotFound := errors.E(context.TODO(), errors.WithoutEvent(), errors.WithCode(errors.RecordNotFound))
|
|
tests := []struct {
|
|
name string
|
|
opt []errors.Option
|
|
want error
|
|
}{
|
|
{
|
|
name: "all-options",
|
|
opt: []errors.Option{
|
|
errors.WithCode(errors.InvalidParameter),
|
|
errors.WithOp("alice.Bob"),
|
|
errors.WithWrap(errRecordNotFound),
|
|
errors.WithMsg("test msg"),
|
|
},
|
|
want: &errors.Err{
|
|
Op: "alice.Bob",
|
|
Wrapped: errRecordNotFound,
|
|
Msg: "test msg",
|
|
Code: errors.InvalidParameter,
|
|
},
|
|
},
|
|
{
|
|
name: "no-options",
|
|
opt: nil,
|
|
want: &errors.Err{
|
|
Code: errors.Unknown,
|
|
},
|
|
},
|
|
{
|
|
name: "withCode",
|
|
opt: []errors.Option{
|
|
errors.WithCode(errors.RecordNotFound),
|
|
},
|
|
want: &errors.Err{
|
|
Code: errors.RecordNotFound,
|
|
},
|
|
},
|
|
{
|
|
name: "uses-wrapped-code",
|
|
opt: []errors.Option{
|
|
errors.WithWrap(errRecordNotFound),
|
|
},
|
|
want: &errors.Err{
|
|
Code: errors.RecordNotFound,
|
|
Wrapped: errRecordNotFound,
|
|
},
|
|
},
|
|
{
|
|
name: "conflicting-withCode-withWrap",
|
|
opt: []errors.Option{
|
|
errors.WithCode(errors.InvalidFieldMask),
|
|
errors.WithWrap(errRecordNotFound),
|
|
},
|
|
want: &errors.Err{
|
|
Code: errors.InvalidFieldMask,
|
|
Wrapped: errRecordNotFound,
|
|
},
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
assert, require := assert.New(t), require.New(t)
|
|
err := errors.EDeprecated(tt.opt...)
|
|
require.Error(err)
|
|
assert.Equal(tt.want, err)
|
|
|
|
err = errors.E(context.TODO(), tt.opt...)
|
|
require.Error(err)
|
|
assert.Equal(tt.want, err)
|
|
})
|
|
}
|
|
t.Run("nil-context", func(t *testing.T) {
|
|
assert, require := assert.New(t), require.New(t)
|
|
//nolint SA1012 intentionally passing a nil context.
|
|
err := errors.E(nil, errors.WithCode(errors.InvalidParameter))
|
|
require.Error(err)
|
|
assert.Equal(&errors.Err{
|
|
Code: errors.InvalidParameter,
|
|
}, err)
|
|
})
|
|
}
|
|
|
|
func Test_NewError(t *testing.T) {
|
|
t.Parallel()
|
|
tests := []struct {
|
|
name string
|
|
code errors.Code
|
|
op errors.Op
|
|
msg string
|
|
opt []errors.Option
|
|
want error
|
|
}{
|
|
{
|
|
name: "all-options",
|
|
code: errors.InvalidParameter,
|
|
op: "alice.Bob",
|
|
msg: "test msg",
|
|
opt: []errors.Option{
|
|
errors.WithWrap(errors.E(context.TODO(), errors.WithoutEvent(), errors.WithCode(errors.RecordNotFound))),
|
|
},
|
|
want: &errors.Err{
|
|
Op: "alice.Bob",
|
|
Wrapped: errors.E(context.TODO(), errors.WithoutEvent(), errors.WithCode(errors.RecordNotFound)),
|
|
Msg: "test msg",
|
|
Code: errors.InvalidParameter,
|
|
},
|
|
},
|
|
{
|
|
name: "empty-op",
|
|
code: errors.InvalidParameter,
|
|
op: "",
|
|
msg: "test msg",
|
|
want: &errors.Err{
|
|
Msg: "test msg",
|
|
Code: errors.InvalidParameter,
|
|
},
|
|
},
|
|
{
|
|
name: "no-options",
|
|
opt: nil,
|
|
want: &errors.Err{
|
|
Code: errors.Unknown,
|
|
},
|
|
},
|
|
{
|
|
name: "conflicting-op",
|
|
op: "alice.Bob",
|
|
opt: []errors.Option{
|
|
errors.WithOp("bab.Op"),
|
|
},
|
|
want: &errors.Err{
|
|
Op: "alice.Bob",
|
|
Code: errors.Unknown,
|
|
},
|
|
},
|
|
{
|
|
name: "conflicting-msg",
|
|
msg: "test msg",
|
|
opt: []errors.Option{
|
|
errors.WithMsg("dont use this message"),
|
|
},
|
|
want: &errors.Err{
|
|
Msg: "test msg",
|
|
Code: errors.Unknown,
|
|
},
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
assert, require := assert.New(t), require.New(t)
|
|
err := errors.NewDeprecated(tt.code, tt.op, tt.msg, tt.opt...)
|
|
require.Error(err)
|
|
assert.Equal(tt.want, err)
|
|
|
|
err = errors.New(context.TODO(), tt.code, tt.op, tt.msg, tt.opt...)
|
|
require.Error(err)
|
|
assert.Equal(tt.want, err)
|
|
})
|
|
}
|
|
}
|
|
|
|
func Test_WrapError(t *testing.T) {
|
|
t.Parallel()
|
|
testErr := errors.E(context.TODO(), errors.WithoutEvent(), errors.WithCode(errors.InvalidParameter), errors.WithOp("alice.Bob"), errors.WithMsg("test msg"))
|
|
tests := []struct {
|
|
name string
|
|
opt []errors.Option
|
|
err error
|
|
op errors.Op
|
|
want error
|
|
}{
|
|
{
|
|
name: "boundary-error",
|
|
err: testErr,
|
|
op: "alice.Bob",
|
|
opt: []errors.Option{
|
|
errors.WithMsg("test msg"),
|
|
},
|
|
want: &errors.Err{
|
|
Wrapped: testErr,
|
|
Op: "alice.Bob",
|
|
Msg: "test msg",
|
|
Code: errors.InvalidParameter,
|
|
},
|
|
},
|
|
{
|
|
name: "boundary-error-no-op",
|
|
err: testErr,
|
|
opt: []errors.Option{
|
|
errors.WithMsg("test msg"),
|
|
},
|
|
want: &errors.Err{
|
|
Wrapped: testErr,
|
|
Msg: "test msg",
|
|
Code: errors.InvalidParameter,
|
|
},
|
|
},
|
|
{
|
|
name: "boundary-error-no-options",
|
|
err: testErr,
|
|
want: &errors.Err{
|
|
Wrapped: testErr,
|
|
Code: errors.InvalidParameter,
|
|
},
|
|
},
|
|
{
|
|
name: "std-error",
|
|
err: fmt.Errorf("std error"),
|
|
want: &errors.Err{
|
|
Wrapped: fmt.Errorf("std error"),
|
|
Code: errors.Unknown,
|
|
},
|
|
},
|
|
{
|
|
name: "conflicting-with-wrap",
|
|
err: testErr,
|
|
opt: []errors.Option{
|
|
errors.WithWrap(fmt.Errorf("dont wrap this error")),
|
|
},
|
|
want: &errors.Err{
|
|
Wrapped: testErr,
|
|
Code: errors.InvalidParameter,
|
|
},
|
|
},
|
|
{
|
|
name: "conflicting-with-op",
|
|
err: testErr,
|
|
op: "alice.Bob",
|
|
opt: []errors.Option{
|
|
errors.WithOp("bad.Op"),
|
|
},
|
|
want: &errors.Err{
|
|
Wrapped: testErr,
|
|
Op: "alice.Bob",
|
|
Code: errors.InvalidParameter,
|
|
},
|
|
},
|
|
{
|
|
name: "NotSpecificIntegrity",
|
|
err: &pgconn.PgError{
|
|
Code: "23001",
|
|
Message: "test msg",
|
|
},
|
|
want: &errors.Err{
|
|
Wrapped: errors.E(context.TODO(), errors.WithoutEvent(), errors.WithCode(errors.NotSpecificIntegrity), errors.WithMsg("test msg")),
|
|
Code: errors.NotSpecificIntegrity,
|
|
},
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
assert, require := assert.New(t), require.New(t)
|
|
err := errors.WrapDeprecated(tt.err, tt.op, tt.opt...)
|
|
require.Error(err)
|
|
assert.Equal(tt.want, err)
|
|
|
|
err = errors.Wrap(context.TODO(), tt.err, tt.op, tt.opt...)
|
|
require.Error(err)
|
|
assert.Equal(tt.want, err)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestError_Info(t *testing.T) {
|
|
t.Parallel()
|
|
tests := []struct {
|
|
name string
|
|
err *errors.Err
|
|
want errors.Code
|
|
}{
|
|
{
|
|
name: "nil",
|
|
err: nil,
|
|
want: errors.Unknown,
|
|
},
|
|
{
|
|
name: "Unknown",
|
|
err: errors.E(context.TODO()).(*errors.Err),
|
|
want: errors.Unknown,
|
|
},
|
|
{
|
|
name: "InvalidParameter",
|
|
err: errors.E(context.TODO(), errors.WithCode(errors.InvalidParameter)).(*errors.Err),
|
|
want: errors.InvalidParameter,
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
assert := assert.New(t)
|
|
assert.Equal(tt.want.Info(), tt.err.Info())
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestError_Error(t *testing.T) {
|
|
t.Parallel()
|
|
tests := []struct {
|
|
name string
|
|
err error
|
|
want string
|
|
}{
|
|
{
|
|
name: "msg",
|
|
err: errors.E(context.TODO(), errors.WithoutEvent(), errors.WithMsg("test msg")),
|
|
want: "test msg: unknown: error #0",
|
|
},
|
|
{
|
|
name: "code",
|
|
err: errors.E(context.TODO(), errors.WithoutEvent(), errors.WithCode(errors.CheckConstraint)),
|
|
want: "constraint check failed, integrity violation: error #1000",
|
|
},
|
|
{
|
|
name: "op-msg-and-code",
|
|
err: errors.E(context.TODO(), errors.WithoutEvent(), errors.WithCode(errors.CheckConstraint), errors.WithOp("alice.bob"), errors.WithMsg("test msg")),
|
|
want: "alice.bob: test msg: integrity violation: error #1000",
|
|
},
|
|
{
|
|
name: "unknown",
|
|
err: errors.EDeprecated(),
|
|
want: "unknown, unknown: error #0",
|
|
},
|
|
{
|
|
name: "wrapped-no-code",
|
|
err: errors.E(context.TODO(), errors.WithoutEvent(), errors.WithWrap(errors.EDeprecated(errors.WithCode(errors.InvalidParameter), errors.WithMsg("wrapped msg"))), errors.WithMsg("test msg")),
|
|
want: "test msg: wrapped msg: parameter violation: error #100",
|
|
},
|
|
{
|
|
name: "wrapped-different-error-codes",
|
|
err: errors.E(context.TODO(), errors.WithoutEvent(), errors.WithCode(errors.CheckConstraint), errors.WithWrap(errors.EDeprecated(errors.WithCode(errors.InvalidParameter), errors.WithMsg("wrapped msg"))), errors.WithMsg("test msg")),
|
|
want: "test msg: integrity violation: error #1000: wrapped msg: parameter violation: error #100",
|
|
},
|
|
{
|
|
name: "wrapped-same-error-codes",
|
|
err: errors.E(context.TODO(), errors.WithoutEvent(), errors.WithCode(errors.CheckConstraint), errors.WithWrap(errors.EDeprecated(errors.WithCode(errors.CheckConstraint), errors.WithMsg("wrapped msg"))), errors.WithMsg("test msg")),
|
|
want: "test msg: wrapped msg: integrity violation: error #1000",
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
assert := assert.New(t)
|
|
got := tt.err.Error()
|
|
assert.Contains(got, tt.want)
|
|
})
|
|
}
|
|
t.Run("nil *Err", func(t *testing.T) {
|
|
assert := assert.New(t)
|
|
var err *errors.Err
|
|
got := err.Error()
|
|
assert.Equal("", got)
|
|
})
|
|
}
|
|
|
|
func TestError_Unwrap(t *testing.T) {
|
|
t.Parallel()
|
|
testErr := errors.EDeprecated(errors.WithMsg("test error"))
|
|
errInvalidParameter := errors.EDeprecated(errors.WithCode(errors.InvalidParameter), errors.WithMsg("test error"))
|
|
|
|
tests := []struct {
|
|
name string
|
|
err error
|
|
want error
|
|
wantIsErr error
|
|
}{
|
|
{
|
|
name: "ErrInvalidParameter",
|
|
err: errors.EDeprecated(errors.WithWrap(errInvalidParameter)),
|
|
want: errInvalidParameter,
|
|
wantIsErr: errInvalidParameter,
|
|
},
|
|
{
|
|
name: "testErr",
|
|
err: testErr,
|
|
want: nil,
|
|
wantIsErr: testErr,
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
assert := assert.New(t)
|
|
err := tt.err.(interface {
|
|
Unwrap() error
|
|
}).Unwrap()
|
|
assert.Equal(tt.want, err)
|
|
assert.True(errors.Is(tt.err, tt.wantIsErr))
|
|
})
|
|
}
|
|
t.Run("nil *Err", func(t *testing.T) {
|
|
assert := assert.New(t)
|
|
var err *errors.Err
|
|
got := err.Unwrap()
|
|
assert.Equal(nil, got)
|
|
})
|
|
}
|
|
|
|
func TestConvertError(t *testing.T) {
|
|
t.Parallel()
|
|
testErr := errors.EDeprecated(errors.WithCode(errors.InvalidParameter), errors.WithOp("alice.Bob"), errors.WithMsg("test msg"))
|
|
const (
|
|
createTable = `
|
|
create table if not exists test_table (
|
|
id bigint generated always as identity primary key,
|
|
name text unique,
|
|
description text not null,
|
|
five text check(length(trim(five)) > 5)
|
|
);
|
|
`
|
|
truncateTable = `truncate test_table;`
|
|
insert = `insert into test_table(name, description, five) values (?, ?, ?)`
|
|
missingTable = `select * from not_a_defined_table`
|
|
)
|
|
ctx := context.Background()
|
|
conn, _ := db.TestSetup(t, "postgres")
|
|
rw := db.New(conn)
|
|
|
|
_, err := rw.Exec(ctx, createTable, nil)
|
|
require.NoError(t, err)
|
|
|
|
tests := []struct {
|
|
name string
|
|
e error
|
|
wantErr error
|
|
}{
|
|
{
|
|
name: "nil",
|
|
e: nil,
|
|
wantErr: nil,
|
|
},
|
|
{
|
|
name: "not-convertible",
|
|
e: stderrors.New("test error"),
|
|
wantErr: nil,
|
|
},
|
|
{
|
|
name: "NotSpecificIntegrity",
|
|
e: &pgconn.PgError{
|
|
Code: "23001",
|
|
},
|
|
wantErr: errors.EDeprecated(errors.WithCode(errors.NotSpecificIntegrity)),
|
|
},
|
|
{
|
|
name: "convert-domain-error",
|
|
e: testErr,
|
|
wantErr: testErr,
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
assert, require := assert.New(t), require.New(t)
|
|
err := errors.Convert(tt.e)
|
|
if tt.wantErr == nil {
|
|
assert.Nil(err)
|
|
return
|
|
}
|
|
require.NotNil(err)
|
|
assert.Equal(tt.wantErr, err)
|
|
})
|
|
}
|
|
t.Run("ErrCodeUnique", func(t *testing.T) {
|
|
assert, require := assert.New(t), require.New(t)
|
|
_, err := rw.Exec(ctx, truncateTable, nil)
|
|
require.NoError(err)
|
|
_, err = rw.Exec(ctx, insert, []interface{}{"alice", "coworker", nil})
|
|
require.NoError(err)
|
|
_, err = rw.Exec(ctx, insert, []interface{}{"alice", "dup coworker", nil})
|
|
require.Error(err)
|
|
|
|
e := errors.Convert(err)
|
|
require.NotNil(e)
|
|
assert.True(errors.Match(errors.T(errors.NotUnique), e))
|
|
assert.Equal("db.Exec: duplicate key value violates unique constraint \"test_table_name_key\": unique constraint violation: integrity violation: error #1002", e.Error())
|
|
})
|
|
t.Run("ErrCodeNotNull", func(t *testing.T) {
|
|
assert, require := assert.New(t), require.New(t)
|
|
_, err := rw.Exec(ctx, truncateTable, nil)
|
|
require.NoError(err)
|
|
_, err = rw.Exec(ctx, insert, []interface{}{"alice", nil, nil})
|
|
require.Error(err)
|
|
|
|
e := errors.Convert(err)
|
|
require.NotNil(e)
|
|
assert.True(errors.Match(errors.T(errors.NotNull), e))
|
|
assert.Equal("db.Exec: description must not be empty: not null constraint violated: integrity violation: error #1001", e.Error())
|
|
})
|
|
t.Run("ErrCodeCheckConstraint", func(t *testing.T) {
|
|
assert, require := assert.New(t), require.New(t)
|
|
_, err := rw.Exec(ctx, truncateTable, nil)
|
|
require.NoError(err)
|
|
_, err = rw.Exec(ctx, insert, []interface{}{"alice", "coworker", "one"})
|
|
require.Error(err)
|
|
|
|
e := errors.Convert(err)
|
|
require.NotNil(e)
|
|
assert.True(errors.Match(errors.T(errors.CheckConstraint), err))
|
|
assert.Equal("db.Exec: test_table_five_check constraint failed: check constraint violated: integrity violation: error #1000", e.Error())
|
|
})
|
|
t.Run("MissingTable", func(t *testing.T) {
|
|
assert, require := assert.New(t), require.New(t)
|
|
_, err := rw.Exec(ctx, missingTable, nil)
|
|
require.Error(err)
|
|
|
|
e := errors.Convert(err)
|
|
require.NotNil(e)
|
|
assert.True(errors.Match(errors.T(errors.MissingTable), e))
|
|
assert.Equal("db.Exec: relation \"not_a_defined_table\" does not exist: integrity violation: error #1004", e.Error())
|
|
})
|
|
}
|