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.
2995 lines
71 KiB
2995 lines
71 KiB
package db
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"strconv"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/golang/protobuf/ptypes"
|
|
"github.com/hashicorp/boundary/internal/db/db_test"
|
|
"github.com/hashicorp/boundary/internal/db/timestamp"
|
|
"github.com/hashicorp/boundary/internal/oplog"
|
|
"github.com/hashicorp/boundary/internal/oplog/store"
|
|
wrapping "github.com/hashicorp/go-kms-wrapping"
|
|
"github.com/hashicorp/go-uuid"
|
|
"github.com/hashicorp/vault/sdk/helper/base62"
|
|
"github.com/jinzhu/gorm"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
"google.golang.org/protobuf/proto"
|
|
)
|
|
|
|
func TestDb_UpdateUnsetField(t *testing.T) {
|
|
db, _ := TestSetup(t, "postgres")
|
|
rw := &Db{
|
|
underlying: db,
|
|
}
|
|
tu := &db_test.TestUser{
|
|
StoreTestUser: &db_test.StoreTestUser{
|
|
PublicId: testId(t),
|
|
Name: "default",
|
|
}}
|
|
require.NoError(t, rw.Create(context.Background(), tu))
|
|
|
|
updatedTu := tu.Clone().(*db_test.TestUser)
|
|
updatedTu.Name = "updated"
|
|
updatedTu.Email = "ignore"
|
|
cnt, err := rw.Update(context.Background(), updatedTu, []string{"Name"}, nil)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, 1, cnt)
|
|
assert.Equal(t, "ignore", updatedTu.Email)
|
|
assert.Equal(t, "updated", updatedTu.Name)
|
|
}
|
|
|
|
func TestDb_Update(t *testing.T) {
|
|
db, _ := TestSetup(t, "postgres")
|
|
now := ×tamp.Timestamp{Timestamp: ptypes.TimestampNow()}
|
|
publicId, err := NewPublicId("testuser")
|
|
require.NoError(t, err)
|
|
id := testId(t)
|
|
|
|
badVersion := uint32(22)
|
|
versionOne := uint32(1)
|
|
versionZero := uint32(0)
|
|
|
|
type args struct {
|
|
i *db_test.TestUser
|
|
fieldMaskPaths []string
|
|
setToNullPaths []string
|
|
opt []Option
|
|
}
|
|
tests := []struct {
|
|
name string
|
|
args args
|
|
want int
|
|
wantErr bool
|
|
wantErrMsg string
|
|
wantName string
|
|
wantEmail string
|
|
wantPhoneNumber string
|
|
wantVersion int
|
|
}{
|
|
{
|
|
name: "simple",
|
|
args: args{
|
|
i: &db_test.TestUser{
|
|
StoreTestUser: &db_test.StoreTestUser{
|
|
Name: "simple-updated" + id,
|
|
Email: "updated" + id,
|
|
PhoneNumber: "updated" + id,
|
|
},
|
|
},
|
|
fieldMaskPaths: []string{"Name", "PhoneNumber"},
|
|
setToNullPaths: []string{"Email"},
|
|
},
|
|
want: 1,
|
|
wantErr: false,
|
|
wantErrMsg: "",
|
|
wantName: "simple-updated" + id,
|
|
wantEmail: "",
|
|
wantPhoneNumber: "updated" + id,
|
|
},
|
|
{
|
|
name: "simple-with-bad-version",
|
|
args: args{
|
|
i: &db_test.TestUser{
|
|
StoreTestUser: &db_test.StoreTestUser{
|
|
Name: "simple-with-bad-version" + id,
|
|
Email: "updated" + id,
|
|
PhoneNumber: "updated" + id,
|
|
},
|
|
},
|
|
fieldMaskPaths: []string{"Name", "PhoneNumber"},
|
|
setToNullPaths: []string{"Email"},
|
|
opt: []Option{WithVersion(&badVersion)},
|
|
},
|
|
want: 0,
|
|
wantErr: false,
|
|
wantErrMsg: "",
|
|
},
|
|
{
|
|
name: "simple-with-zero-version",
|
|
args: args{
|
|
i: &db_test.TestUser{
|
|
StoreTestUser: &db_test.StoreTestUser{
|
|
Name: "simple-with-bad-version" + id,
|
|
Email: "updated" + id,
|
|
PhoneNumber: "updated" + id,
|
|
},
|
|
},
|
|
fieldMaskPaths: []string{"Name", "PhoneNumber"},
|
|
setToNullPaths: []string{"Email"},
|
|
opt: []Option{WithVersion(&versionZero)},
|
|
},
|
|
want: 0,
|
|
wantErr: true,
|
|
wantErrMsg: "update: with version option is zero: invalid parameter",
|
|
},
|
|
{
|
|
name: "simple-with-version",
|
|
args: args{
|
|
i: &db_test.TestUser{
|
|
StoreTestUser: &db_test.StoreTestUser{
|
|
Name: "simple-with-version" + id,
|
|
Email: "updated" + id,
|
|
PhoneNumber: "updated" + id,
|
|
},
|
|
},
|
|
fieldMaskPaths: []string{"Name", "PhoneNumber"},
|
|
setToNullPaths: []string{"Email"},
|
|
opt: []Option{WithVersion(&versionOne)},
|
|
},
|
|
want: 1,
|
|
wantErr: false,
|
|
wantErrMsg: "",
|
|
wantName: "simple-with-version" + id,
|
|
wantEmail: "",
|
|
wantPhoneNumber: "updated" + id,
|
|
wantVersion: 2,
|
|
},
|
|
{
|
|
name: "simple-with-where",
|
|
args: args{
|
|
i: &db_test.TestUser{
|
|
StoreTestUser: &db_test.StoreTestUser{
|
|
Name: "simple-with-where" + id,
|
|
Email: "updated" + id,
|
|
PhoneNumber: "updated" + id,
|
|
},
|
|
},
|
|
fieldMaskPaths: []string{"Name", "PhoneNumber"},
|
|
setToNullPaths: []string{"Email"},
|
|
opt: []Option{WithWhere("email = ? and phone_number = ?", id, id)},
|
|
},
|
|
want: 1,
|
|
wantErr: false,
|
|
wantErrMsg: "",
|
|
wantName: "simple-with-where" + id,
|
|
wantEmail: "",
|
|
wantPhoneNumber: "updated" + id,
|
|
wantVersion: 2,
|
|
},
|
|
{
|
|
name: "simple-with-where-and-version",
|
|
args: args{
|
|
i: &db_test.TestUser{
|
|
StoreTestUser: &db_test.StoreTestUser{
|
|
Name: "simple-with-where-and-version" + id,
|
|
Email: "updated" + id,
|
|
PhoneNumber: "updated" + id,
|
|
},
|
|
},
|
|
fieldMaskPaths: []string{"Name", "PhoneNumber"},
|
|
setToNullPaths: []string{"Email"},
|
|
opt: []Option{WithWhere("email = ? and phone_number = ?", id, id), WithVersion(&versionOne)},
|
|
},
|
|
want: 1,
|
|
wantErr: false,
|
|
wantErrMsg: "",
|
|
wantName: "simple-with-where-and-version" + id,
|
|
wantEmail: "",
|
|
wantPhoneNumber: "updated" + id,
|
|
wantVersion: 2,
|
|
},
|
|
{
|
|
name: "bad-with-where",
|
|
args: args{
|
|
i: &db_test.TestUser{
|
|
StoreTestUser: &db_test.StoreTestUser{
|
|
Name: "bad-with-where" + id,
|
|
Email: "updated" + id,
|
|
PhoneNumber: "updated" + id,
|
|
},
|
|
},
|
|
fieldMaskPaths: []string{"Name", "PhoneNumber"},
|
|
setToNullPaths: []string{"Email"},
|
|
opt: []Option{WithWhere("foo = ? and phone_number = ?", id, id)},
|
|
},
|
|
want: 0,
|
|
wantErr: true,
|
|
wantErrMsg: `update: failed: pq: column "foo" does not exist`,
|
|
},
|
|
{
|
|
name: "multiple-null",
|
|
args: args{
|
|
i: &db_test.TestUser{
|
|
StoreTestUser: &db_test.StoreTestUser{
|
|
Name: "multiple-null-updated" + id,
|
|
Email: "updated" + id,
|
|
PhoneNumber: "updated" + id,
|
|
},
|
|
},
|
|
fieldMaskPaths: []string{"Name"},
|
|
setToNullPaths: []string{"Email", "PhoneNumber"},
|
|
},
|
|
want: 1,
|
|
wantErr: false,
|
|
wantErrMsg: "",
|
|
wantName: "multiple-null-updated" + id,
|
|
wantEmail: "",
|
|
wantPhoneNumber: "",
|
|
},
|
|
{
|
|
name: "non-updatable",
|
|
args: args{
|
|
i: &db_test.TestUser{
|
|
StoreTestUser: &db_test.StoreTestUser{
|
|
Name: "non-updatable" + id,
|
|
Email: "updated" + id,
|
|
PhoneNumber: "updated" + id,
|
|
PublicId: publicId,
|
|
CreateTime: now,
|
|
UpdateTime: now,
|
|
},
|
|
},
|
|
fieldMaskPaths: []string{"Name", "PhoneNumber", "CreateTime", "UpdateTime", "PublicId"},
|
|
setToNullPaths: []string{"Email"},
|
|
},
|
|
want: 1,
|
|
wantErr: false,
|
|
wantErrMsg: "",
|
|
wantName: "non-updatable" + id,
|
|
wantEmail: "",
|
|
wantPhoneNumber: "updated" + id,
|
|
},
|
|
{
|
|
name: "primary-key",
|
|
args: args{
|
|
i: &db_test.TestUser{
|
|
StoreTestUser: &db_test.StoreTestUser{
|
|
Name: "primary-key" + id,
|
|
Email: "updated" + id,
|
|
PhoneNumber: "updated" + id,
|
|
PublicId: publicId,
|
|
CreateTime: now,
|
|
UpdateTime: now,
|
|
},
|
|
},
|
|
fieldMaskPaths: []string{"Id"},
|
|
setToNullPaths: []string{"Email"},
|
|
},
|
|
want: 0,
|
|
wantErr: true,
|
|
wantErrMsg: "update: not allowed on primary key field Id: invalid field mask",
|
|
},
|
|
{
|
|
name: "both are missing",
|
|
args: args{
|
|
i: &db_test.TestUser{
|
|
StoreTestUser: &db_test.StoreTestUser{
|
|
Name: "both are missing-updated" + id,
|
|
Email: id,
|
|
PhoneNumber: id,
|
|
},
|
|
},
|
|
fieldMaskPaths: nil,
|
|
setToNullPaths: []string{},
|
|
},
|
|
want: 0,
|
|
wantErr: true,
|
|
wantErrMsg: "update: both fieldMaskPaths and setToNullPaths are missing",
|
|
},
|
|
{
|
|
name: "i is nil",
|
|
args: args{
|
|
i: nil,
|
|
fieldMaskPaths: []string{"Name", "PhoneNumber"},
|
|
setToNullPaths: []string{"Email"},
|
|
},
|
|
want: 0,
|
|
wantErr: true,
|
|
wantErrMsg: "update: interface is missing invalid parameter",
|
|
},
|
|
{
|
|
name: "only read-only",
|
|
args: args{
|
|
i: &db_test.TestUser{
|
|
StoreTestUser: &db_test.StoreTestUser{
|
|
Name: "only read-only" + id,
|
|
Email: id,
|
|
PhoneNumber: id,
|
|
},
|
|
},
|
|
fieldMaskPaths: []string{"CreateTime"},
|
|
setToNullPaths: []string{"UpdateTime"},
|
|
},
|
|
want: 0,
|
|
wantErr: true,
|
|
wantErrMsg: "update: after filtering non-updated fields, there are no fields left in fieldMaskPaths or setToNullPaths",
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
assert, require := assert.New(t), require.New(t)
|
|
rw := &Db{
|
|
underlying: db,
|
|
}
|
|
u := testUser(t, db, tt.name+id, id, id)
|
|
|
|
if tt.args.i != nil {
|
|
tt.args.i.Id = u.Id
|
|
tt.args.i.PublicId = u.PublicId
|
|
}
|
|
rowsUpdated, err := rw.Update(context.Background(), tt.args.i, tt.args.fieldMaskPaths, tt.args.setToNullPaths, tt.args.opt...)
|
|
if tt.wantErr {
|
|
require.Error(err)
|
|
assert.Equal(tt.want, rowsUpdated)
|
|
assert.Equal(tt.wantErrMsg, err.Error())
|
|
return
|
|
}
|
|
require.NoError(err)
|
|
assert.Equal(tt.want, rowsUpdated)
|
|
if tt.want == 0 {
|
|
return
|
|
}
|
|
foundUser, err := db_test.NewTestUser()
|
|
require.NoError(err)
|
|
foundUser.PublicId = tt.args.i.PublicId
|
|
where := "public_id = ?"
|
|
for _, f := range tt.args.setToNullPaths {
|
|
switch {
|
|
case strings.EqualFold(f, "phonenumber"):
|
|
f = "phone_number"
|
|
}
|
|
where = fmt.Sprintf("%s and %s is null", where, f)
|
|
}
|
|
err = rw.LookupWhere(context.Background(), foundUser, where, tt.args.i.PublicId)
|
|
require.NoError(err)
|
|
assert.Equal(tt.args.i.Id, foundUser.Id)
|
|
assert.Equal(tt.wantName, foundUser.Name)
|
|
assert.Equal(tt.wantEmail, foundUser.Email)
|
|
assert.Equal(tt.wantPhoneNumber, foundUser.PhoneNumber)
|
|
assert.NotEqual(now, foundUser.CreateTime)
|
|
assert.NotEqual(now, foundUser.UpdateTime)
|
|
assert.NotEqual(publicId, foundUser.PublicId)
|
|
assert.Equal(u.Version+1, foundUser.Version)
|
|
})
|
|
}
|
|
t.Run("no-version-field", func(t *testing.T) {
|
|
assert := assert.New(t)
|
|
w := Db{underlying: db}
|
|
id, err := uuid.GenerateUUID()
|
|
assert.NoError(err)
|
|
car := testCar(t, db, "foo-"+id, id, int32(100))
|
|
|
|
car.Name = "friendly-" + id
|
|
versionOne := uint32(1)
|
|
rowsUpdated, err := w.Update(context.Background(), car, []string{"Name"}, nil, WithVersion(&versionOne))
|
|
assert.Error(err)
|
|
assert.Equal(0, rowsUpdated)
|
|
})
|
|
t.Run("valid-WithOplog", func(t *testing.T) {
|
|
assert, require := assert.New(t), require.New(t)
|
|
w := Db{underlying: db}
|
|
id, err := uuid.GenerateUUID()
|
|
require.NoError(err)
|
|
user := testUser(t, db, "foo-"+id, id, id)
|
|
|
|
user.Name = "friendly-" + id
|
|
rowsUpdated, err := w.Update(context.Background(), user, []string{"Name"}, nil,
|
|
// write oplogs for this update
|
|
WithOplog(
|
|
TestWrapper(t),
|
|
oplog.Metadata{
|
|
"key-only": nil,
|
|
"deployment": []string{"amex"},
|
|
"project": []string{"central-info-systems", "local-info-systems"},
|
|
"resource-public-id": []string{user.PublicId},
|
|
"op-type": []string{oplog.OpType_OP_TYPE_UPDATE.String()},
|
|
}),
|
|
)
|
|
require.NoError(err)
|
|
assert.Equal(1, rowsUpdated)
|
|
|
|
foundUser, err := db_test.NewTestUser()
|
|
require.NoError(err)
|
|
foundUser.PublicId = user.PublicId
|
|
err = w.LookupByPublicId(context.Background(), foundUser)
|
|
require.NoError(err)
|
|
assert.Equal(foundUser.Name, user.Name)
|
|
|
|
err = TestVerifyOplog(t, &w, user.PublicId, WithOperation(oplog.OpType_OP_TYPE_UPDATE), WithCreateNotBefore(10*time.Second))
|
|
require.NoError(err)
|
|
})
|
|
t.Run("both-WithOplog-NewOplogMsg", func(t *testing.T) {
|
|
assert, require := assert.New(t), require.New(t)
|
|
w := Db{underlying: db}
|
|
id, err := uuid.GenerateUUID()
|
|
require.NoError(err)
|
|
user, err := db_test.NewTestUser()
|
|
require.NoError(err)
|
|
user.Name = "foo-" + id
|
|
createMsg := oplog.Message{}
|
|
err = w.Create(
|
|
context.Background(),
|
|
user,
|
|
NewOplogMsg(&createMsg),
|
|
)
|
|
require.NoError(err)
|
|
|
|
updateMsg := oplog.Message{}
|
|
user.Name = "friendly-" + id
|
|
rowsUpdated, err := w.Update(
|
|
context.Background(),
|
|
user,
|
|
[]string{"Name"}, nil,
|
|
NewOplogMsg(&updateMsg),
|
|
WithOplog(TestWrapper(t), oplog.Metadata{"alice": []string{"bob"}}),
|
|
)
|
|
require.Error(err)
|
|
assert.Equal(0, rowsUpdated)
|
|
assert.True(errors.Is(err, ErrInvalidParameter))
|
|
})
|
|
t.Run("valid-NewOplogMsg", func(t *testing.T) {
|
|
assert, require := assert.New(t), require.New(t)
|
|
w := Db{underlying: db}
|
|
|
|
ticket, err := w.GetTicket(&db_test.TestUser{})
|
|
require.NoError(err)
|
|
|
|
id, err := uuid.GenerateUUID()
|
|
require.NoError(err)
|
|
user, err := db_test.NewTestUser()
|
|
require.NoError(err)
|
|
user.Name = "foo-" + id
|
|
createMsg := oplog.Message{}
|
|
err = w.Create(
|
|
context.Background(),
|
|
user,
|
|
NewOplogMsg(&createMsg),
|
|
)
|
|
require.NoError(err)
|
|
|
|
updateMsg := oplog.Message{}
|
|
user.Name = "friendly-" + id
|
|
rowsUpdated, err := w.Update(context.Background(), user, []string{"Name"}, nil, NewOplogMsg(&updateMsg))
|
|
require.NoError(err)
|
|
assert.Equal(1, rowsUpdated)
|
|
|
|
foundUser, err := db_test.NewTestUser()
|
|
require.NoError(err)
|
|
foundUser.PublicId = user.PublicId
|
|
err = w.LookupByPublicId(context.Background(), foundUser)
|
|
require.NoError(err)
|
|
assert.Equal(foundUser.Name, user.Name)
|
|
|
|
metadata := oplog.Metadata{
|
|
"resource-public-id": []string{user.PublicId},
|
|
// "op-type": []string{oplog.OpType_OP_TYPE_UPDATE.String()},
|
|
}
|
|
err = w.WriteOplogEntryWith(context.Background(), TestWrapper(t), ticket, metadata, []*oplog.Message{&createMsg, &updateMsg})
|
|
require.NoError(err)
|
|
|
|
err = TestVerifyOplog(t, &w, user.PublicId, WithOperation(oplog.OpType_OP_TYPE_UNSPECIFIED), WithCreateNotBefore(10*time.Second))
|
|
assert.NoError(err)
|
|
})
|
|
t.Run("vet-for-write", func(t *testing.T) {
|
|
assert, require := assert.New(t), require.New(t)
|
|
w := Db{underlying: db}
|
|
id, err := uuid.GenerateUUID()
|
|
assert.NoError(err)
|
|
user := &testUserWithVet{
|
|
PublicId: id,
|
|
Name: id,
|
|
PhoneNumber: id,
|
|
Email: id,
|
|
}
|
|
err = db.Create(user).Error
|
|
require.NoError(err)
|
|
|
|
user.Name = "friendly-" + id
|
|
rowsUpdated, err := w.Update(context.Background(), user, []string{"Name"}, nil)
|
|
require.NoError(err)
|
|
assert.Equal(1, rowsUpdated)
|
|
|
|
foundUser := &testUserWithVet{PublicId: user.PublicId}
|
|
require.NoError(err)
|
|
foundUser.PublicId = user.PublicId
|
|
err = w.LookupByPublicId(context.Background(), foundUser)
|
|
require.NoError(err)
|
|
assert.Equal(foundUser.Name, user.Name)
|
|
|
|
user.PublicId = "not-allowed-by-vet-for-write"
|
|
rowsUpdated, err = w.Update(context.Background(), user, []string{"PublicId"}, nil)
|
|
require.Error(err)
|
|
assert.Equal(0, rowsUpdated)
|
|
})
|
|
t.Run("nil-tx", func(t *testing.T) {
|
|
assert := assert.New(t)
|
|
w := Db{underlying: nil}
|
|
id, err := uuid.GenerateUUID()
|
|
assert.NoError(err)
|
|
|
|
user := testUser(t, db, "foo-"+id, id, id)
|
|
rowsUpdated, err := w.Update(context.Background(), user, []string{"Name"}, nil)
|
|
assert.Error(err)
|
|
assert.Equal(0, rowsUpdated)
|
|
assert.Equal("update: missing underlying db invalid parameter", err.Error())
|
|
})
|
|
t.Run("no-wrapper-WithOplog", func(t *testing.T) {
|
|
assert, require := assert.New(t), require.New(t)
|
|
w := Db{underlying: db}
|
|
id, err := uuid.GenerateUUID()
|
|
require.NoError(err)
|
|
user := testUser(t, db, "foo-"+id, id, id)
|
|
|
|
user.Name = "friendly-" + id
|
|
rowsUpdated, err := w.Update(context.Background(), user, []string{"Name"},
|
|
nil,
|
|
WithOplog(
|
|
nil,
|
|
oplog.Metadata{
|
|
"key-only": nil,
|
|
"deployment": []string{"amex"},
|
|
"project": []string{"central-info-systems", "local-info-systems"},
|
|
}),
|
|
)
|
|
require.Error(err)
|
|
assert.Equal(0, rowsUpdated)
|
|
assert.Equal("update: oplog validation failed: error no wrapper WithOplog: invalid parameter", err.Error())
|
|
})
|
|
t.Run("no-metadata-WithOplog", func(t *testing.T) {
|
|
assert, require := assert.New(t), require.New(t)
|
|
w := Db{underlying: db}
|
|
id, err := uuid.GenerateUUID()
|
|
require.NoError(err)
|
|
user := testUser(t, db, "foo-"+id, id, id)
|
|
user.Name = "friendly-" + id
|
|
rowsUpdated, err := w.Update(context.Background(), user, []string{"Name"}, nil,
|
|
WithOplog(
|
|
TestWrapper(t),
|
|
nil,
|
|
),
|
|
)
|
|
require.Error(err)
|
|
assert.Equal(0, rowsUpdated)
|
|
assert.Equal("update: oplog validation failed: error no metadata for WithOplog: invalid parameter", err.Error())
|
|
})
|
|
}
|
|
|
|
// testUserWithVet gives us a model that implements VetForWrite() without any
|
|
// cyclic dependencies.
|
|
type testUserWithVet struct {
|
|
Id uint32 `gorm:"primary_key"`
|
|
PublicId string
|
|
Name string `gorm:"default:null"`
|
|
PhoneNumber string `gorm:"default:null"`
|
|
Email string `gorm:"default:null"`
|
|
}
|
|
|
|
func (u *testUserWithVet) GetPublicId() string {
|
|
return u.PublicId
|
|
}
|
|
func (u *testUserWithVet) TableName() string {
|
|
return "db_test_user"
|
|
}
|
|
func (u *testUserWithVet) VetForWrite(ctx context.Context, r Reader, opType OpType, opt ...Option) error {
|
|
if u.PublicId == "" {
|
|
return errors.New("public id is empty string for user write")
|
|
}
|
|
if opType == UpdateOp {
|
|
dbOptions := GetOpts(opt...)
|
|
for _, path := range dbOptions.WithFieldMaskPaths {
|
|
switch path {
|
|
case "PublicId":
|
|
return errors.New("you cannot change the public id")
|
|
}
|
|
}
|
|
}
|
|
if opType == CreateOp {
|
|
if u.Id != 0 {
|
|
return errors.New("id is a db sequence")
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func TestDb_Create(t *testing.T) {
|
|
// intentionally not run with t.Parallel so we don't need to use DoTx for the Create tests
|
|
db, _ := TestSetup(t, "postgres")
|
|
t.Run("simple", func(t *testing.T) {
|
|
assert, require := assert.New(t), require.New(t)
|
|
w := Db{underlying: db}
|
|
id, err := uuid.GenerateUUID()
|
|
require.NoError(err)
|
|
user, err := db_test.NewTestUser()
|
|
require.NoError(err)
|
|
ts := ×tamp.Timestamp{Timestamp: ptypes.TimestampNow()}
|
|
user.CreateTime = ts
|
|
user.UpdateTime = ts
|
|
user.Name = "foo-" + id
|
|
err = w.Create(context.Background(), user)
|
|
require.NoError(err)
|
|
assert.NotEmpty(user.Id)
|
|
// make sure the database controlled the timestamp values
|
|
assert.NotEqual(ts, user.GetCreateTime())
|
|
assert.NotEqual(ts, user.GetUpdateTime())
|
|
|
|
foundUser, err := db_test.NewTestUser()
|
|
require.NoError(err)
|
|
foundUser.PublicId = user.PublicId
|
|
err = w.LookupByPublicId(context.Background(), foundUser)
|
|
require.NoError(err)
|
|
assert.Equal(foundUser.Id, user.Id)
|
|
})
|
|
t.Run("valid-WithOplog", func(t *testing.T) {
|
|
assert, require := assert.New(t), require.New(t)
|
|
w := Db{underlying: db}
|
|
id, err := uuid.GenerateUUID()
|
|
require.NoError(err)
|
|
user, err := db_test.NewTestUser()
|
|
require.NoError(err)
|
|
user.Name = "foo-" + id
|
|
err = w.Create(
|
|
context.Background(),
|
|
user,
|
|
WithOplog(
|
|
TestWrapper(t),
|
|
oplog.Metadata{
|
|
"key-only": nil,
|
|
"deployment": []string{"amex"},
|
|
"project": []string{"central-info-systems", "local-info-systems"},
|
|
},
|
|
),
|
|
)
|
|
require.NoError(err)
|
|
require.NotEmpty(user.Id)
|
|
|
|
foundUser, err := db_test.NewTestUser()
|
|
require.NoError(err)
|
|
foundUser.PublicId = user.PublicId
|
|
err = w.LookupByPublicId(context.Background(), foundUser)
|
|
require.NoError(err)
|
|
assert.Equal(foundUser.Id, user.Id)
|
|
})
|
|
t.Run("both-Oplog-NewOplogMsg", func(t *testing.T) {
|
|
assert, require := assert.New(t), require.New(t)
|
|
w := Db{underlying: db}
|
|
id, err := uuid.GenerateUUID()
|
|
require.NoError(err)
|
|
user, err := db_test.NewTestUser()
|
|
require.NoError(err)
|
|
user.Name = "foo-" + id
|
|
createMsg := oplog.Message{}
|
|
err = w.Create(
|
|
context.Background(),
|
|
user,
|
|
NewOplogMsg(&createMsg),
|
|
WithOplog(TestWrapper(t), oplog.Metadata{"alice": []string{"bob"}}),
|
|
)
|
|
require.Error(err)
|
|
assert.True(errors.Is(err, ErrInvalidParameter))
|
|
})
|
|
t.Run("valid-NewOplogMsg", func(t *testing.T) {
|
|
assert, require := assert.New(t), require.New(t)
|
|
w := Db{underlying: db}
|
|
|
|
ticket, err := w.GetTicket(&db_test.TestUser{})
|
|
require.NoError(err)
|
|
|
|
id, err := uuid.GenerateUUID()
|
|
require.NoError(err)
|
|
user, err := db_test.NewTestUser()
|
|
require.NoError(err)
|
|
user.Name = "foo-" + id
|
|
createMsg := oplog.Message{}
|
|
err = w.Create(
|
|
context.Background(),
|
|
user,
|
|
NewOplogMsg(&createMsg),
|
|
)
|
|
require.NoError(err)
|
|
|
|
updateMsg := oplog.Message{}
|
|
user.Name = "friendly-" + id
|
|
rowsUpdated, err := w.Update(context.Background(), user, []string{"Name"}, nil, NewOplogMsg(&updateMsg))
|
|
require.NoError(err)
|
|
assert.Equal(1, rowsUpdated)
|
|
|
|
foundUser, err := db_test.NewTestUser()
|
|
require.NoError(err)
|
|
foundUser.PublicId = user.PublicId
|
|
err = w.LookupByPublicId(context.Background(), foundUser)
|
|
require.NoError(err)
|
|
assert.Equal(foundUser.Name, user.Name)
|
|
|
|
metadata := oplog.Metadata{
|
|
"resource-public-id": []string{user.PublicId},
|
|
// "op-type": []string{oplog.OpType_OP_TYPE_UPDATE.String()},
|
|
}
|
|
err = w.WriteOplogEntryWith(context.Background(), TestWrapper(t), ticket, metadata, []*oplog.Message{&createMsg, &updateMsg})
|
|
require.NoError(err)
|
|
|
|
err = TestVerifyOplog(t, &w, user.PublicId, WithOperation(oplog.OpType_OP_TYPE_UNSPECIFIED), WithCreateNotBefore(10*time.Second))
|
|
require.NoError(err)
|
|
})
|
|
t.Run("no-wrapper-WithOplog", func(t *testing.T) {
|
|
assert, require := assert.New(t), require.New(t)
|
|
w := Db{underlying: db}
|
|
id, err := uuid.GenerateUUID()
|
|
require.NoError(err)
|
|
user, err := db_test.NewTestUser()
|
|
require.NoError(err)
|
|
user.Name = "foo-" + id
|
|
err = w.Create(
|
|
context.Background(),
|
|
user,
|
|
WithOplog(
|
|
nil,
|
|
oplog.Metadata{
|
|
"key-only": nil,
|
|
"deployment": []string{"amex"},
|
|
"project": []string{"central-info-systems", "local-info-systems"},
|
|
},
|
|
),
|
|
)
|
|
require.Error(err)
|
|
assert.Equal("create: oplog validation failed: error no wrapper WithOplog: invalid parameter", err.Error())
|
|
})
|
|
t.Run("no-metadata-WithOplog", func(t *testing.T) {
|
|
assert, require := assert.New(t), require.New(t)
|
|
w := Db{underlying: db}
|
|
id, err := uuid.GenerateUUID()
|
|
require.NoError(err)
|
|
user, err := db_test.NewTestUser()
|
|
require.NoError(err)
|
|
user.Name = "foo-" + id
|
|
err = w.Create(
|
|
context.Background(),
|
|
user,
|
|
WithOplog(
|
|
TestWrapper(t),
|
|
nil,
|
|
),
|
|
)
|
|
require.Error(err)
|
|
assert.Equal("create: oplog validation failed: error no metadata for WithOplog: invalid parameter", err.Error())
|
|
})
|
|
t.Run("nil-tx", func(t *testing.T) {
|
|
assert, require := assert.New(t), require.New(t)
|
|
w := Db{underlying: nil}
|
|
id, err := uuid.GenerateUUID()
|
|
require.NoError(err)
|
|
user, err := db_test.NewTestUser()
|
|
require.NoError(err)
|
|
user.Name = "foo-" + id
|
|
err = w.Create(context.Background(), user)
|
|
require.Error(err)
|
|
assert.Equal("create: missing underlying db: invalid parameter", err.Error())
|
|
})
|
|
}
|
|
|
|
func TestDb_LookupByName(t *testing.T) {
|
|
t.Parallel()
|
|
db, _ := TestSetup(t, "postgres")
|
|
t.Run("simple", func(t *testing.T) {
|
|
assert, require := assert.New(t), require.New(t)
|
|
w := Db{underlying: db}
|
|
id, err := uuid.GenerateUUID()
|
|
require.NoError(err)
|
|
user, err := db_test.NewTestUser()
|
|
require.NoError(err)
|
|
user.Name = "fn-" + id
|
|
err = w.Create(context.Background(), user)
|
|
require.NoError(err)
|
|
assert.NotEmpty(user.Id)
|
|
|
|
foundUser, err := db_test.NewTestUser()
|
|
require.NoError(err)
|
|
foundUser.Name = "fn-" + id
|
|
err = w.LookupByName(context.Background(), foundUser)
|
|
require.NoError(err)
|
|
assert.Equal(foundUser.Id, user.Id)
|
|
})
|
|
t.Run("tx-nil,", func(t *testing.T) {
|
|
assert, require := assert.New(t), require.New(t)
|
|
w := Db{}
|
|
foundUser, err := db_test.NewTestUser()
|
|
require.NoError(err)
|
|
foundUser.Name = "fn-name"
|
|
err = w.LookupByName(context.Background(), foundUser)
|
|
require.Error(err)
|
|
assert.Equal("error underlying db nil for lookup by name", err.Error())
|
|
})
|
|
t.Run("no-friendly-name-set", func(t *testing.T) {
|
|
assert, require := assert.New(t), require.New(t)
|
|
w := Db{underlying: db}
|
|
foundUser, err := db_test.NewTestUser()
|
|
require.NoError(err)
|
|
err = w.LookupByName(context.Background(), foundUser)
|
|
require.Error(err)
|
|
assert.Equal("error name empty string for lookup by name", err.Error())
|
|
})
|
|
t.Run("not-found", func(t *testing.T) {
|
|
assert, require := assert.New(t), require.New(t)
|
|
w := Db{underlying: db}
|
|
id, err := uuid.GenerateUUID()
|
|
require.NoError(err)
|
|
|
|
foundUser, err := db_test.NewTestUser()
|
|
require.NoError(err)
|
|
foundUser.Name = "fn-" + id
|
|
err = w.LookupByName(context.Background(), foundUser)
|
|
require.Error(err)
|
|
assert.Equal(ErrRecordNotFound, err)
|
|
})
|
|
}
|
|
|
|
func TestDb_LookupByPublicId(t *testing.T) {
|
|
t.Parallel()
|
|
db, _ := TestSetup(t, "postgres")
|
|
t.Run("simple", func(t *testing.T) {
|
|
assert, require := assert.New(t), require.New(t)
|
|
w := Db{underlying: db}
|
|
id, err := uuid.GenerateUUID()
|
|
require.NoError(err)
|
|
user, err := db_test.NewTestUser()
|
|
require.NoError(err)
|
|
user.Name = "foo-" + id
|
|
err = w.Create(context.Background(), user)
|
|
require.NoError(err)
|
|
assert.NotEmpty(user.PublicId)
|
|
|
|
foundUser, err := db_test.NewTestUser()
|
|
require.NoError(err)
|
|
foundUser.PublicId = user.PublicId
|
|
err = w.LookupByPublicId(context.Background(), foundUser)
|
|
require.NoError(err)
|
|
assert.Equal(foundUser.Id, user.Id)
|
|
})
|
|
t.Run("tx-nil,", func(t *testing.T) {
|
|
assert, require := assert.New(t), require.New(t)
|
|
w := Db{}
|
|
foundUser, err := db_test.NewTestUser()
|
|
require.NoError(err)
|
|
err = w.LookupByPublicId(context.Background(), foundUser)
|
|
require.Error(err)
|
|
assert.Equal("lookup by id: underlying db nil invalid parameter", err.Error())
|
|
})
|
|
t.Run("no-public-id-set", func(t *testing.T) {
|
|
assert, require := assert.New(t), require.New(t)
|
|
w := Db{underlying: db}
|
|
foundUser, err := db_test.NewTestUser()
|
|
foundUser.PublicId = ""
|
|
require.NoError(err)
|
|
err = w.LookupByPublicId(context.Background(), foundUser)
|
|
require.Error(err)
|
|
assert.Equal("lookup by id: primary key unset invalid parameter", err.Error())
|
|
})
|
|
t.Run("not-found", func(t *testing.T) {
|
|
assert, require := assert.New(t), require.New(t)
|
|
w := Db{underlying: db}
|
|
id, err := uuid.GenerateUUID()
|
|
require.NoError(err)
|
|
foundUser, err := db_test.NewTestUser()
|
|
require.NoError(err)
|
|
foundUser.PublicId = id
|
|
err = w.LookupByPublicId(context.Background(), foundUser)
|
|
require.Error(err)
|
|
assert.Equal(ErrRecordNotFound, err)
|
|
})
|
|
}
|
|
|
|
func TestDb_LookupWhere(t *testing.T) {
|
|
t.Parallel()
|
|
db, _ := TestSetup(t, "postgres")
|
|
t.Run("simple", func(t *testing.T) {
|
|
assert, require := assert.New(t), require.New(t)
|
|
w := Db{underlying: db}
|
|
id, err := uuid.GenerateUUID()
|
|
require.NoError(err)
|
|
user, err := db_test.NewTestUser()
|
|
require.NoError(err)
|
|
user.Name = "foo-" + id
|
|
err = w.Create(context.Background(), user)
|
|
require.NoError(err)
|
|
assert.NotEmpty(user.PublicId)
|
|
|
|
var foundUser db_test.TestUser
|
|
err = w.LookupWhere(context.Background(), &foundUser, "public_id = ?", user.PublicId)
|
|
require.NoError(err)
|
|
assert.Equal(foundUser.Id, user.Id)
|
|
})
|
|
t.Run("tx-nil,", func(t *testing.T) {
|
|
assert, require := assert.New(t), require.New(t)
|
|
w := Db{}
|
|
var foundUser db_test.TestUser
|
|
err := w.LookupWhere(context.Background(), &foundUser, "public_id = ?", 1)
|
|
require.Error(err)
|
|
assert.Equal("error underlying db nil for lookup by", err.Error())
|
|
})
|
|
t.Run("not-found", func(t *testing.T) {
|
|
assert, require := assert.New(t), require.New(t)
|
|
w := Db{underlying: db}
|
|
id, err := uuid.GenerateUUID()
|
|
require.NoError(err)
|
|
|
|
var foundUser db_test.TestUser
|
|
err = w.LookupWhere(context.Background(), &foundUser, "public_id = ?", id)
|
|
require.Error(err)
|
|
assert.Equal(ErrRecordNotFound, err)
|
|
assert.True(errors.Is(err, ErrRecordNotFound))
|
|
})
|
|
t.Run("bad-where", func(t *testing.T) {
|
|
require := require.New(t)
|
|
w := Db{underlying: db}
|
|
id, err := uuid.GenerateUUID()
|
|
require.NoError(err)
|
|
|
|
var foundUser db_test.TestUser
|
|
err = w.LookupWhere(context.Background(), &foundUser, "? = ?", id)
|
|
require.Error(err)
|
|
})
|
|
}
|
|
|
|
func TestDb_SearchWhere(t *testing.T) {
|
|
t.Parallel()
|
|
db, _ := TestSetup(t, "postgres")
|
|
knownUser := testUser(t, db, "zedUser", "", "")
|
|
|
|
type args struct {
|
|
where string
|
|
arg []interface{}
|
|
opt []Option
|
|
}
|
|
tests := []struct {
|
|
name string
|
|
db Db
|
|
createCnt int
|
|
args args
|
|
wantCnt int
|
|
wantErr bool
|
|
wantNameOrder bool
|
|
}{
|
|
{
|
|
name: "no-limit",
|
|
db: Db{underlying: db},
|
|
createCnt: 10,
|
|
args: args{
|
|
where: "1=1",
|
|
opt: []Option{WithLimit(-1), WithOrder("name asc")},
|
|
},
|
|
wantCnt: 11, // there's an additional knownUser
|
|
wantErr: false,
|
|
wantNameOrder: true,
|
|
},
|
|
{
|
|
name: "custom-limit",
|
|
db: Db{underlying: db},
|
|
createCnt: 10,
|
|
args: args{
|
|
where: "1=1",
|
|
opt: []Option{WithLimit(3)},
|
|
},
|
|
wantCnt: 3,
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "simple",
|
|
db: Db{underlying: db},
|
|
createCnt: 1,
|
|
args: args{
|
|
where: "public_id = ?",
|
|
arg: []interface{}{knownUser.PublicId},
|
|
opt: []Option{WithLimit(3)},
|
|
},
|
|
wantCnt: 1,
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "not-found",
|
|
db: Db{underlying: db},
|
|
createCnt: 1,
|
|
args: args{
|
|
where: "public_id = ?",
|
|
arg: []interface{}{"bad-id"},
|
|
opt: []Option{WithLimit(3)},
|
|
},
|
|
wantCnt: 0,
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "bad-where",
|
|
db: Db{underlying: db},
|
|
createCnt: 1,
|
|
args: args{
|
|
where: "bad_column_name = ?",
|
|
arg: []interface{}{knownUser.PublicId},
|
|
opt: []Option{WithLimit(3)},
|
|
},
|
|
wantCnt: 0,
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "nil-underlying",
|
|
db: Db{},
|
|
createCnt: 1,
|
|
args: args{
|
|
where: "public_id = ?",
|
|
arg: []interface{}{knownUser.PublicId},
|
|
opt: []Option{WithLimit(3)},
|
|
},
|
|
wantCnt: 0,
|
|
wantErr: true,
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
assert, require := assert.New(t), require.New(t)
|
|
testUsers := []*db_test.TestUser{}
|
|
for i := 0; i < tt.createCnt; i++ {
|
|
testUsers = append(testUsers, testUser(t, db, tt.name+strconv.Itoa(i), "", ""))
|
|
}
|
|
assert.Equal(tt.createCnt, len(testUsers))
|
|
|
|
var foundUsers []db_test.TestUser
|
|
db.LogMode(true)
|
|
defer db.LogMode(false)
|
|
err := tt.db.SearchWhere(context.Background(), &foundUsers, tt.args.where, tt.args.arg, tt.args.opt...)
|
|
if tt.wantErr {
|
|
require.Error(err)
|
|
return
|
|
}
|
|
require.NoError(err)
|
|
assert.Equal(tt.wantCnt, len(foundUsers))
|
|
if tt.wantNameOrder {
|
|
assert.Equal(tt.name+strconv.Itoa(0), foundUsers[0].Name)
|
|
for i, u := range foundUsers {
|
|
if u.Name != "zedUser" {
|
|
assert.Equal(tt.name+strconv.Itoa(i), u.Name)
|
|
}
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestDb_DB(t *testing.T) {
|
|
t.Parallel()
|
|
db, _ := TestSetup(t, "postgres")
|
|
t.Run("valid", func(t *testing.T) {
|
|
require := require.New(t)
|
|
w := Db{underlying: db}
|
|
d, err := w.DB()
|
|
require.NoError(err)
|
|
require.NotNil(d)
|
|
err = d.Ping()
|
|
require.NoError(err)
|
|
})
|
|
t.Run("nil-tx", func(t *testing.T) {
|
|
assert, require := assert.New(t), require.New(t)
|
|
w := Db{underlying: nil}
|
|
d, err := w.DB()
|
|
require.Error(err)
|
|
assert.Nil(d)
|
|
assert.Equal("missing underlying db: invalid parameter", err.Error())
|
|
})
|
|
}
|
|
|
|
func TestDb_Exec(t *testing.T) {
|
|
t.Parallel()
|
|
t.Run("update", func(t *testing.T) {
|
|
db, _ := TestSetup(t, "postgres")
|
|
require := require.New(t)
|
|
w := Db{underlying: db}
|
|
id := testId(t)
|
|
user, err := db_test.NewTestUser()
|
|
require.NoError(err)
|
|
user.Name = "foo-" + id
|
|
err = w.Create(context.Background(), user)
|
|
require.NoError(err)
|
|
require.NotEmpty(user.Id)
|
|
rowsAffected, err := w.Exec("update db_test_user set name = ? where public_id = ?", []interface{}{"updated-" + id, user.PublicId})
|
|
require.NoError(err)
|
|
require.Equal(1, rowsAffected)
|
|
})
|
|
}
|
|
func TestDb_DoTx(t *testing.T) {
|
|
t.Parallel()
|
|
db, _ := TestSetup(t, "postgres")
|
|
t.Run("valid-with-10-retries", func(t *testing.T) {
|
|
assert, require := assert.New(t), require.New(t)
|
|
w := &Db{underlying: db}
|
|
attempts := 0
|
|
got, err := w.DoTx(context.Background(), 10, ExpBackoff{},
|
|
func(Reader, Writer) error {
|
|
attempts += 1
|
|
if attempts < 9 {
|
|
return oplog.ErrTicketAlreadyRedeemed
|
|
}
|
|
return nil
|
|
})
|
|
require.NoError(err)
|
|
assert.Equal(8, got.Retries)
|
|
assert.Equal(9, attempts) // attempted 1 + 8 retries
|
|
})
|
|
t.Run("valid-with-1-retries", func(t *testing.T) {
|
|
assert, require := assert.New(t), require.New(t)
|
|
w := &Db{underlying: db}
|
|
attempts := 0
|
|
got, err := w.DoTx(context.Background(), 1, ExpBackoff{},
|
|
func(Reader, Writer) error {
|
|
attempts += 1
|
|
if attempts < 2 {
|
|
return oplog.ErrTicketAlreadyRedeemed
|
|
}
|
|
return nil
|
|
})
|
|
require.NoError(err)
|
|
assert.Equal(1, got.Retries)
|
|
assert.Equal(2, attempts) // attempted 1 + 8 retries
|
|
})
|
|
t.Run("valid-with-2-retries", func(t *testing.T) {
|
|
assert, require := assert.New(t), require.New(t)
|
|
w := &Db{underlying: db}
|
|
attempts := 0
|
|
got, err := w.DoTx(context.Background(), 3, ExpBackoff{},
|
|
func(Reader, Writer) error {
|
|
attempts += 1
|
|
if attempts < 3 {
|
|
return oplog.ErrTicketAlreadyRedeemed
|
|
}
|
|
return nil
|
|
})
|
|
require.NoError(err)
|
|
assert.Equal(2, got.Retries)
|
|
assert.Equal(3, attempts) // attempted 1 + 8 retries
|
|
})
|
|
t.Run("valid-with-4-retries", func(t *testing.T) {
|
|
assert, require := assert.New(t), require.New(t)
|
|
w := &Db{underlying: db}
|
|
attempts := 0
|
|
got, err := w.DoTx(context.Background(), 4, ExpBackoff{},
|
|
func(Reader, Writer) error {
|
|
attempts += 1
|
|
if attempts < 4 {
|
|
return oplog.ErrTicketAlreadyRedeemed
|
|
}
|
|
return nil
|
|
})
|
|
require.NoError(err)
|
|
assert.Equal(3, got.Retries)
|
|
assert.Equal(4, attempts) // attempted 1 + 8 retries
|
|
})
|
|
t.Run("zero-retries", func(t *testing.T) {
|
|
assert, require := assert.New(t), require.New(t)
|
|
w := &Db{underlying: db}
|
|
attempts := 0
|
|
got, err := w.DoTx(context.Background(), 0, ExpBackoff{}, func(Reader, Writer) error { attempts += 1; return nil })
|
|
require.NoError(err)
|
|
assert.Equal(RetryInfo{}, got)
|
|
assert.Equal(1, attempts)
|
|
})
|
|
t.Run("nil-tx", func(t *testing.T) {
|
|
assert, require := assert.New(t), require.New(t)
|
|
w := &Db{nil}
|
|
attempts := 0
|
|
got, err := w.DoTx(context.Background(), 1, ExpBackoff{}, func(Reader, Writer) error { attempts += 1; return nil })
|
|
require.Error(err)
|
|
assert.Equal(RetryInfo{}, got)
|
|
assert.Equal("do underlying db is nil", err.Error())
|
|
})
|
|
t.Run("not-a-retry-err", func(t *testing.T) {
|
|
assert, require := assert.New(t), require.New(t)
|
|
w := &Db{underlying: db}
|
|
got, err := w.DoTx(context.Background(), 1, ExpBackoff{}, func(Reader, Writer) error { return errors.New("not a retry error") })
|
|
require.Error(err)
|
|
assert.Equal(RetryInfo{}, got)
|
|
assert.NotEqual(err, oplog.ErrTicketAlreadyRedeemed)
|
|
})
|
|
t.Run("too-many-retries", func(t *testing.T) {
|
|
assert, require := assert.New(t), require.New(t)
|
|
w := &Db{underlying: db}
|
|
attempts := 0
|
|
got, err := w.DoTx(context.Background(), 2, ExpBackoff{}, func(Reader, Writer) error { attempts += 1; return oplog.ErrTicketAlreadyRedeemed })
|
|
require.Error(err)
|
|
assert.Equal(3, got.Retries)
|
|
assert.Equal("Too many retries: 3 of 3", err.Error())
|
|
})
|
|
t.Run("updating-good-bad-good", func(t *testing.T) {
|
|
assert, require := assert.New(t), require.New(t)
|
|
rw := Db{underlying: db}
|
|
id, err := uuid.GenerateUUID()
|
|
require.NoError(err)
|
|
user, err := db_test.NewTestUser()
|
|
require.NoError(err)
|
|
user.Name = "foo-" + id
|
|
err = rw.Create(context.Background(), user)
|
|
require.NoError(err)
|
|
require.NotZero(user.Id)
|
|
|
|
_, err = rw.DoTx(context.Background(), 10, ExpBackoff{}, func(r Reader, w Writer) error {
|
|
user.Name = "friendly-" + id
|
|
rowsUpdated, err := w.Update(context.Background(), user, []string{"Name"}, nil)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if rowsUpdated != 1 {
|
|
return fmt.Errorf("error in number of rows updated %d", rowsUpdated)
|
|
}
|
|
return nil
|
|
})
|
|
require.NoError(err)
|
|
|
|
foundUser, err := db_test.NewTestUser()
|
|
assert.NoError(err)
|
|
foundUser.PublicId = user.PublicId
|
|
err = rw.LookupByPublicId(context.Background(), foundUser)
|
|
require.NoError(err)
|
|
assert.Equal(foundUser.Name, user.Name)
|
|
|
|
user2, err := db_test.NewTestUser()
|
|
require.NoError(err)
|
|
_, err = rw.DoTx(context.Background(), 10, ExpBackoff{}, func(_ Reader, w Writer) error {
|
|
user2.Name = "friendly2-" + id
|
|
rowsUpdated, err := w.Update(context.Background(), user2, []string{"Name"}, nil)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if rowsUpdated != 1 {
|
|
return fmt.Errorf("error in number of rows updated %d", rowsUpdated)
|
|
}
|
|
return nil
|
|
})
|
|
require.Error(err)
|
|
err = rw.LookupByPublicId(context.Background(), foundUser)
|
|
require.NoError(err)
|
|
assert.NotEqual(foundUser.Name, user2.Name)
|
|
|
|
_, err = rw.DoTx(context.Background(), 10, ExpBackoff{}, func(r Reader, w Writer) error {
|
|
user.Name = "friendly2-" + id
|
|
rowsUpdated, err := w.Update(context.Background(), user, []string{"Name"}, nil)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if rowsUpdated != 1 {
|
|
return fmt.Errorf("error in number of rows updated %d", rowsUpdated)
|
|
}
|
|
return nil
|
|
})
|
|
require.NoError(err)
|
|
err = rw.LookupByPublicId(context.Background(), foundUser)
|
|
require.NoError(err)
|
|
assert.Equal(foundUser.Name, user.Name)
|
|
})
|
|
}
|
|
|
|
func TestDb_Delete(t *testing.T) {
|
|
db, _ := TestSetup(t, "postgres")
|
|
newUser := func() *db_test.TestUser {
|
|
w := &Db{
|
|
underlying: db,
|
|
}
|
|
u, err := db_test.NewTestUser()
|
|
require.NoError(t, err)
|
|
err = w.Create(context.Background(), u)
|
|
require.NoError(t, err)
|
|
return u
|
|
}
|
|
notFoundUser := func() *db_test.TestUser {
|
|
u, err := db_test.NewTestUser()
|
|
require.NoError(t, err)
|
|
u.Id = 1111111
|
|
return u
|
|
}
|
|
|
|
newMetadata := func(publicId string) oplog.Metadata {
|
|
return oplog.Metadata{
|
|
"op-type": []string{oplog.OpType_OP_TYPE_DELETE.String()},
|
|
"resource-public-id": []string{publicId},
|
|
}
|
|
}
|
|
type args struct {
|
|
i *db_test.TestUser
|
|
opt []Option
|
|
metadata func(publicId string) oplog.Metadata
|
|
}
|
|
tests := []struct {
|
|
name string
|
|
underlying *gorm.DB
|
|
wrapper wrapping.Wrapper
|
|
args args
|
|
want int
|
|
wantOplog bool
|
|
wantErr bool
|
|
wantErrIs error
|
|
}{
|
|
{
|
|
name: "simple-no-oplog",
|
|
underlying: db,
|
|
wrapper: TestWrapper(t),
|
|
args: args{
|
|
i: newUser(),
|
|
},
|
|
want: 1,
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "valid-with-oplog",
|
|
underlying: db,
|
|
wrapper: TestWrapper(t),
|
|
args: args{
|
|
i: newUser(),
|
|
metadata: newMetadata,
|
|
},
|
|
wantOplog: true,
|
|
want: 1,
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "nil-wrapper",
|
|
underlying: db,
|
|
wrapper: nil,
|
|
args: args{
|
|
i: newUser(),
|
|
metadata: newMetadata,
|
|
},
|
|
wantOplog: true,
|
|
want: 0,
|
|
wantErr: true,
|
|
wantErrIs: ErrInvalidParameter,
|
|
},
|
|
{
|
|
name: "nil-metadata",
|
|
underlying: db,
|
|
wrapper: nil,
|
|
args: args{
|
|
i: newUser(),
|
|
metadata: func(string) oplog.Metadata { return nil },
|
|
},
|
|
wantOplog: true,
|
|
want: 0,
|
|
wantErr: true,
|
|
wantErrIs: ErrInvalidParameter,
|
|
},
|
|
{
|
|
name: "nil-underlying",
|
|
underlying: nil,
|
|
wrapper: TestWrapper(t),
|
|
args: args{
|
|
i: newUser(),
|
|
},
|
|
want: 0,
|
|
wantErr: true,
|
|
wantErrIs: ErrInvalidParameter,
|
|
},
|
|
{
|
|
name: "not-found",
|
|
underlying: db,
|
|
wrapper: TestWrapper(t),
|
|
args: args{
|
|
i: notFoundUser(),
|
|
},
|
|
want: 0,
|
|
wantErr: false,
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
require := require.New(t)
|
|
assert := assert.New(t)
|
|
rw := &Db{
|
|
underlying: tt.underlying,
|
|
}
|
|
if tt.wantOplog {
|
|
metadata := tt.args.metadata(tt.args.i.PublicId)
|
|
opLog := WithOplog(tt.wrapper, metadata)
|
|
tt.args.opt = append(tt.args.opt, opLog)
|
|
}
|
|
got, err := rw.Delete(context.Background(), tt.args.i, tt.args.opt...)
|
|
assert.Equal(tt.want, got)
|
|
if tt.wantErr {
|
|
require.Error(err)
|
|
if tt.wantErrIs != nil {
|
|
assert.Truef(errors.Is(err, tt.wantErrIs), "received unexpected error: %v", err)
|
|
}
|
|
err := TestVerifyOplog(t, rw, tt.args.i.GetPublicId(), WithOperation(oplog.OpType_OP_TYPE_DELETE), WithCreateNotBefore(5*time.Second))
|
|
assert.Error(err)
|
|
return
|
|
}
|
|
require.NoError(err)
|
|
assert.Equal(tt.want, got)
|
|
|
|
foundUser := tt.args.i.Clone().(*db_test.TestUser)
|
|
foundUser.PublicId = tt.args.i.PublicId
|
|
err = rw.LookupByPublicId(context.Background(), foundUser)
|
|
assert.Error(err)
|
|
assert.Equal(ErrRecordNotFound, err)
|
|
|
|
err = TestVerifyOplog(t, rw, tt.args.i.GetPublicId(), WithOperation(oplog.OpType_OP_TYPE_DELETE), WithCreateNotBefore(5*time.Second))
|
|
switch {
|
|
case tt.wantOplog:
|
|
assert.NoError(err)
|
|
default:
|
|
assert.Error(err)
|
|
}
|
|
})
|
|
t.Run("both-WithOplog-NewOplogMsg", func(t *testing.T) {
|
|
assert, require := assert.New(t), require.New(t)
|
|
w := Db{underlying: db}
|
|
id, err := uuid.GenerateUUID()
|
|
require.NoError(err)
|
|
user, err := db_test.NewTestUser()
|
|
require.NoError(err)
|
|
user.Name = "foo-" + id
|
|
createMsg := oplog.Message{}
|
|
err = w.Create(
|
|
context.Background(),
|
|
user,
|
|
NewOplogMsg(&createMsg),
|
|
)
|
|
require.NoError(err)
|
|
|
|
deleteMsg := oplog.Message{}
|
|
rowsDeleted, err := w.Delete(context.Background(), user, NewOplogMsg(&deleteMsg), WithOplog(TestWrapper(t), oplog.Metadata{"alice": []string{"bob"}}))
|
|
require.Error(err)
|
|
assert.Equal(0, rowsDeleted)
|
|
assert.True(errors.Is(err, ErrInvalidParameter))
|
|
})
|
|
t.Run("valid-NewOplogMsg", func(t *testing.T) {
|
|
assert, require := assert.New(t), require.New(t)
|
|
w := Db{underlying: db}
|
|
|
|
ticket, err := w.GetTicket(&db_test.TestUser{})
|
|
require.NoError(err)
|
|
|
|
id, err := uuid.GenerateUUID()
|
|
require.NoError(err)
|
|
user, err := db_test.NewTestUser()
|
|
require.NoError(err)
|
|
user.Name = "foo-" + id
|
|
createMsg := oplog.Message{}
|
|
err = w.Create(
|
|
context.Background(),
|
|
user,
|
|
NewOplogMsg(&createMsg),
|
|
)
|
|
require.NoError(err)
|
|
|
|
deleteMsg := oplog.Message{}
|
|
rowsDeleted, err := w.Delete(context.Background(), user, NewOplogMsg(&deleteMsg))
|
|
require.NoError(err)
|
|
assert.Equal(1, rowsDeleted)
|
|
|
|
foundUser, err := db_test.NewTestUser()
|
|
require.NoError(err)
|
|
foundUser.PublicId = user.PublicId
|
|
err = w.LookupByPublicId(context.Background(), foundUser)
|
|
require.Error(err)
|
|
assert.True(errors.Is(err, ErrRecordNotFound))
|
|
|
|
metadata := oplog.Metadata{
|
|
"resource-public-id": []string{user.PublicId},
|
|
}
|
|
err = w.WriteOplogEntryWith(context.Background(), TestWrapper(t), ticket, metadata, []*oplog.Message{&createMsg, &deleteMsg})
|
|
require.NoError(err)
|
|
|
|
err = TestVerifyOplog(t, &w, user.PublicId, WithOperation(oplog.OpType_OP_TYPE_UNSPECIFIED), WithCreateNotBefore(10*time.Second))
|
|
require.NoError(err)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestDb_ScanRows(t *testing.T) {
|
|
t.Parallel()
|
|
db, _ := TestSetup(t, "postgres")
|
|
t.Run("valid", func(t *testing.T) {
|
|
assert, require := assert.New(t), require.New(t)
|
|
w := Db{underlying: db}
|
|
user, err := db_test.NewTestUser()
|
|
require.NoError(err)
|
|
err = w.Create(context.Background(), user)
|
|
require.NoError(err)
|
|
assert.NotEmpty(user.Id)
|
|
|
|
tx, err := w.DB()
|
|
require.NoError(err)
|
|
where := "select * from db_test_user where name in ($1, $2)"
|
|
rows, err := tx.Query(where, "alice", "bob")
|
|
require.NoError(err)
|
|
defer func() { err := rows.Close(); assert.NoError(err) }()
|
|
for rows.Next() {
|
|
u, err := db_test.NewTestUser()
|
|
require.NoError(err)
|
|
|
|
// scan the row into your Gorm struct
|
|
err = w.ScanRows(rows, &u)
|
|
require.NoError(err)
|
|
assert.Equal(user.PublicId, u.PublicId)
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestDb_CreateItems(t *testing.T) {
|
|
db, _ := TestSetup(t, "postgres")
|
|
testOplogResourceId := testId(t)
|
|
|
|
createFn := func() []interface{} {
|
|
results := []interface{}{}
|
|
for i := 0; i < 10; i++ {
|
|
u, err := db_test.NewTestUser()
|
|
require.NoError(t, err)
|
|
results = append(results, u)
|
|
}
|
|
return results
|
|
}
|
|
createMixedFn := func() []interface{} {
|
|
u, err := db_test.NewTestUser()
|
|
require.NoError(t, err)
|
|
c, err := db_test.NewTestCar()
|
|
require.NoError(t, err)
|
|
return []interface{}{
|
|
u,
|
|
c,
|
|
}
|
|
}
|
|
|
|
returnedMsgs := []*oplog.Message{}
|
|
|
|
type args struct {
|
|
createItems []interface{}
|
|
opt []Option
|
|
}
|
|
tests := []struct {
|
|
name string
|
|
underlying *gorm.DB
|
|
args args
|
|
wantOplogId string
|
|
wantOplogMsgs bool
|
|
wantErr bool
|
|
wantErrIs error
|
|
}{
|
|
{
|
|
name: "simple",
|
|
underlying: db,
|
|
args: args{
|
|
createItems: createFn(),
|
|
},
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "withOplog",
|
|
underlying: db,
|
|
args: args{
|
|
createItems: createFn(),
|
|
opt: []Option{
|
|
WithOplog(
|
|
TestWrapper(t),
|
|
oplog.Metadata{
|
|
"resource-public-id": []string{testOplogResourceId},
|
|
"op-type": []string{oplog.OpType_OP_TYPE_CREATE.String()},
|
|
},
|
|
),
|
|
},
|
|
},
|
|
wantOplogId: testOplogResourceId,
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "NewOplogMsgs",
|
|
underlying: db,
|
|
args: args{
|
|
createItems: createFn(),
|
|
opt: []Option{
|
|
NewOplogMsgs(&returnedMsgs),
|
|
},
|
|
},
|
|
wantOplogMsgs: true,
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "withOplog and NewOplogMsgs",
|
|
underlying: db,
|
|
args: args{
|
|
createItems: createFn(),
|
|
opt: []Option{
|
|
NewOplogMsgs(&[]*oplog.Message{}),
|
|
WithOplog(
|
|
TestWrapper(t),
|
|
oplog.Metadata{
|
|
"resource-public-id": []string{testOplogResourceId},
|
|
"op-type": []string{oplog.OpType_OP_TYPE_CREATE.String()},
|
|
},
|
|
),
|
|
},
|
|
},
|
|
wantErrIs: ErrInvalidParameter,
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "mixed items",
|
|
underlying: db,
|
|
args: args{
|
|
createItems: createMixedFn(),
|
|
},
|
|
wantErr: true,
|
|
wantErrIs: ErrInvalidParameter,
|
|
},
|
|
{
|
|
name: "bad oplog opt: nil metadata",
|
|
underlying: nil,
|
|
args: args{
|
|
createItems: createFn(),
|
|
opt: []Option{
|
|
WithOplog(
|
|
TestWrapper(t),
|
|
nil,
|
|
),
|
|
},
|
|
},
|
|
wantErr: true,
|
|
wantErrIs: ErrInvalidParameter,
|
|
},
|
|
{
|
|
name: "bad oplog opt: nil wrapper",
|
|
underlying: nil,
|
|
args: args{
|
|
createItems: createFn(),
|
|
opt: []Option{
|
|
WithOplog(
|
|
nil,
|
|
oplog.Metadata{
|
|
"resource-public-id": []string{"doesn't matter since wrapper is nil"},
|
|
"op-type": []string{oplog.OpType_OP_TYPE_CREATE.String()},
|
|
},
|
|
),
|
|
},
|
|
},
|
|
wantErr: true,
|
|
wantErrIs: ErrInvalidParameter,
|
|
},
|
|
{
|
|
name: "bad opt: WithLookup",
|
|
underlying: nil,
|
|
args: args{
|
|
createItems: createFn(),
|
|
opt: []Option{WithLookup(true)},
|
|
},
|
|
wantErr: true,
|
|
wantErrIs: ErrInvalidParameter,
|
|
},
|
|
{
|
|
name: "nil underlying",
|
|
underlying: nil,
|
|
args: args{
|
|
createItems: createFn(),
|
|
},
|
|
wantErr: true,
|
|
wantErrIs: ErrInvalidParameter,
|
|
},
|
|
{
|
|
name: "empty items",
|
|
underlying: db,
|
|
args: args{
|
|
createItems: []interface{}{},
|
|
},
|
|
wantErr: true,
|
|
wantErrIs: ErrInvalidParameter,
|
|
},
|
|
{
|
|
name: "nil items",
|
|
underlying: db,
|
|
args: args{
|
|
createItems: nil,
|
|
},
|
|
wantErr: true,
|
|
wantErrIs: ErrInvalidParameter,
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
assert, require := assert.New(t), require.New(t)
|
|
rw := &Db{
|
|
underlying: tt.underlying,
|
|
}
|
|
err := rw.CreateItems(context.Background(), tt.args.createItems, tt.args.opt...)
|
|
if tt.wantErr {
|
|
require.Error(err)
|
|
if tt.wantErrIs != nil {
|
|
assert.Truef(errors.Is(err, tt.wantErrIs), "unexpected error: %s", err.Error())
|
|
}
|
|
return
|
|
}
|
|
require.NoError(err)
|
|
for _, item := range tt.args.createItems {
|
|
u := db_test.AllocTestUser()
|
|
u.PublicId = item.(*db_test.TestUser).PublicId
|
|
err := rw.LookupByPublicId(context.Background(), &u)
|
|
assert.NoError(err)
|
|
if _, ok := item.(*db_test.TestUser); ok {
|
|
assert.Truef(proto.Equal(item.(*db_test.TestUser).StoreTestUser, u.StoreTestUser), "%s and %s should be equal", item, u)
|
|
}
|
|
}
|
|
if tt.wantOplogId != "" {
|
|
err = TestVerifyOplog(t, rw, tt.wantOplogId, WithOperation(oplog.OpType_OP_TYPE_CREATE), WithCreateNotBefore(10*time.Second))
|
|
assert.NoError(err)
|
|
}
|
|
if tt.wantOplogMsgs {
|
|
assert.Equal(len(tt.args.createItems), len(returnedMsgs))
|
|
for _, m := range returnedMsgs {
|
|
assert.Equal(m.OpType, oplog.OpType_OP_TYPE_CREATE)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestDb_DeleteItems(t *testing.T) {
|
|
db, _ := TestSetup(t, "postgres")
|
|
testOplogResourceId := testId(t)
|
|
|
|
createFn := func() []interface{} {
|
|
results := []interface{}{}
|
|
for i := 0; i < 10; i++ {
|
|
u := testUser(t, db, "", "", "")
|
|
results = append(results, u)
|
|
}
|
|
return results
|
|
}
|
|
|
|
returnedMsgs := []*oplog.Message{}
|
|
|
|
type args struct {
|
|
deleteItems []interface{}
|
|
opt []Option
|
|
}
|
|
tests := []struct {
|
|
name string
|
|
underlying *gorm.DB
|
|
args args
|
|
wantRowsDeleted int
|
|
wantOplogId string
|
|
wantOplogMsgs bool
|
|
wantErr bool
|
|
wantErrIs error
|
|
}{
|
|
{
|
|
name: "simple",
|
|
underlying: db,
|
|
args: args{
|
|
deleteItems: createFn(),
|
|
},
|
|
wantRowsDeleted: 10,
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "NewOplogMsgs",
|
|
underlying: db,
|
|
args: args{
|
|
deleteItems: createFn(),
|
|
opt: []Option{
|
|
NewOplogMsgs(&returnedMsgs),
|
|
},
|
|
},
|
|
wantRowsDeleted: 10,
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "withOplog and NewOplogMsgs",
|
|
underlying: db,
|
|
args: args{
|
|
deleteItems: createFn(),
|
|
opt: []Option{
|
|
NewOplogMsgs(&[]*oplog.Message{}),
|
|
WithOplog(
|
|
TestWrapper(t),
|
|
oplog.Metadata{
|
|
"resource-public-id": []string{testOplogResourceId},
|
|
"op-type": []string{oplog.OpType_OP_TYPE_DELETE.String()},
|
|
},
|
|
),
|
|
},
|
|
},
|
|
wantErr: true,
|
|
wantErrIs: ErrInvalidParameter,
|
|
},
|
|
{
|
|
name: "withOplog",
|
|
underlying: db,
|
|
args: args{
|
|
deleteItems: createFn(),
|
|
opt: []Option{
|
|
WithOplog(
|
|
TestWrapper(t),
|
|
oplog.Metadata{
|
|
"resource-public-id": []string{testOplogResourceId},
|
|
"op-type": []string{oplog.OpType_OP_TYPE_DELETE.String()},
|
|
},
|
|
),
|
|
},
|
|
},
|
|
wantRowsDeleted: 10,
|
|
wantOplogId: testOplogResourceId,
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "bad oplog opt: nil metadata",
|
|
underlying: nil,
|
|
args: args{
|
|
deleteItems: createFn(),
|
|
opt: []Option{
|
|
WithOplog(
|
|
TestWrapper(t),
|
|
nil,
|
|
),
|
|
},
|
|
},
|
|
wantErr: true,
|
|
wantErrIs: ErrInvalidParameter,
|
|
},
|
|
{
|
|
name: "bad oplog opt: nil wrapper",
|
|
underlying: nil,
|
|
args: args{
|
|
deleteItems: createFn(),
|
|
opt: []Option{
|
|
WithOplog(
|
|
nil,
|
|
oplog.Metadata{
|
|
"resource-public-id": []string{"doesn't matter since wrapper is nil"},
|
|
"op-type": []string{oplog.OpType_OP_TYPE_CREATE.String()},
|
|
},
|
|
),
|
|
},
|
|
},
|
|
wantErr: true,
|
|
wantErrIs: ErrInvalidParameter,
|
|
},
|
|
{
|
|
name: "bad opt: WithLookup",
|
|
underlying: nil,
|
|
args: args{
|
|
deleteItems: createFn(),
|
|
opt: []Option{WithLookup(true)},
|
|
},
|
|
wantErr: true,
|
|
wantErrIs: ErrInvalidParameter,
|
|
},
|
|
{
|
|
name: "nil underlying",
|
|
underlying: nil,
|
|
args: args{
|
|
deleteItems: createFn(),
|
|
},
|
|
wantErr: true,
|
|
wantErrIs: ErrInvalidParameter,
|
|
},
|
|
{
|
|
name: "empty items",
|
|
underlying: db,
|
|
args: args{
|
|
deleteItems: []interface{}{},
|
|
},
|
|
wantErr: true,
|
|
wantErrIs: ErrInvalidParameter,
|
|
},
|
|
{
|
|
name: "nil items",
|
|
underlying: db,
|
|
args: args{
|
|
deleteItems: nil,
|
|
},
|
|
wantErr: true,
|
|
wantErrIs: ErrInvalidParameter,
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
assert, require := assert.New(t), require.New(t)
|
|
rw := &Db{
|
|
underlying: tt.underlying,
|
|
}
|
|
rowsDeleted, err := rw.DeleteItems(context.Background(), tt.args.deleteItems, tt.args.opt...)
|
|
if tt.wantErr {
|
|
require.Error(err)
|
|
if tt.wantErrIs != nil {
|
|
assert.Truef(errors.Is(err, tt.wantErrIs), "unexpected error: %s", err.Error())
|
|
}
|
|
return
|
|
}
|
|
require.NoError(err)
|
|
assert.Equal(tt.wantRowsDeleted, rowsDeleted)
|
|
for _, item := range tt.args.deleteItems {
|
|
u := db_test.AllocTestUser()
|
|
u.PublicId = item.(*db_test.TestUser).PublicId
|
|
err := rw.LookupByPublicId(context.Background(), &u)
|
|
require.Error(err)
|
|
require.Truef(errors.Is(err, ErrRecordNotFound), "found item %s that should be deleted", u.PublicId)
|
|
}
|
|
if tt.wantOplogId != "" {
|
|
err = TestVerifyOplog(t, rw, tt.wantOplogId, WithOperation(oplog.OpType_OP_TYPE_DELETE), WithCreateNotBefore(10*time.Second))
|
|
assert.NoError(err)
|
|
}
|
|
if tt.wantOplogMsgs {
|
|
assert.Equal(len(tt.args.deleteItems), len(returnedMsgs))
|
|
for _, m := range returnedMsgs {
|
|
assert.Equal(m.OpType, oplog.OpType_OP_TYPE_DELETE)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func testUser(t *testing.T, conn *gorm.DB, name, email, phoneNumber string) *db_test.TestUser {
|
|
t.Helper()
|
|
require := require.New(t)
|
|
|
|
publicId, err := base62.Random(20)
|
|
require.NoError(err)
|
|
u := &db_test.TestUser{
|
|
StoreTestUser: &db_test.StoreTestUser{
|
|
PublicId: publicId,
|
|
Name: name,
|
|
Email: email,
|
|
PhoneNumber: phoneNumber,
|
|
},
|
|
}
|
|
if conn != nil {
|
|
err = conn.Create(u).Error
|
|
require.NoError(err)
|
|
}
|
|
return u
|
|
}
|
|
func testCar(t *testing.T, conn *gorm.DB, name, model string, mpg int32) *db_test.TestCar {
|
|
t.Helper()
|
|
require := require.New(t)
|
|
|
|
publicId, err := base62.Random(20)
|
|
require.NoError(err)
|
|
c := &db_test.TestCar{
|
|
StoreTestCar: &db_test.StoreTestCar{
|
|
PublicId: publicId,
|
|
Name: name,
|
|
Model: model,
|
|
Mpg: mpg,
|
|
},
|
|
}
|
|
if conn != nil {
|
|
err = conn.Create(c).Error
|
|
require.NoError(err)
|
|
}
|
|
return c
|
|
}
|
|
|
|
func testId(t *testing.T) string {
|
|
t.Helper()
|
|
require := require.New(t)
|
|
id, err := uuid.GenerateUUID()
|
|
require.NoError(err)
|
|
return id
|
|
}
|
|
|
|
func testScooter(t *testing.T, conn *gorm.DB, model string, mpg int32) *db_test.TestScooter {
|
|
t.Helper()
|
|
require := require.New(t)
|
|
|
|
privateId, err := base62.Random(20)
|
|
require.NoError(err)
|
|
u := &db_test.TestScooter{
|
|
StoreTestScooter: &db_test.StoreTestScooter{
|
|
PrivateId: privateId,
|
|
Model: model,
|
|
Mpg: mpg,
|
|
},
|
|
}
|
|
if conn != nil {
|
|
err = conn.Create(u).Error
|
|
require.NoError(err)
|
|
}
|
|
return u
|
|
}
|
|
|
|
func TestDb_LookupById(t *testing.T) {
|
|
t.Parallel()
|
|
db, _ := TestSetup(t, "postgres")
|
|
scooter := testScooter(t, db, "", 0)
|
|
user := testUser(t, db, "", "", "")
|
|
type args struct {
|
|
resourceWithIder interface{}
|
|
opt []Option
|
|
}
|
|
tests := []struct {
|
|
name string
|
|
underlying *gorm.DB
|
|
args args
|
|
wantErr bool
|
|
want proto.Message
|
|
wantIsErr error
|
|
}{
|
|
{
|
|
name: "simple-private-id",
|
|
underlying: db,
|
|
args: args{
|
|
resourceWithIder: scooter,
|
|
},
|
|
wantErr: false,
|
|
want: scooter,
|
|
},
|
|
{
|
|
name: "simple-public-id",
|
|
underlying: db,
|
|
args: args{
|
|
resourceWithIder: user,
|
|
},
|
|
wantErr: false,
|
|
want: user,
|
|
},
|
|
{
|
|
name: "missing-public-id",
|
|
underlying: db,
|
|
args: args{
|
|
resourceWithIder: &db_test.TestUser{
|
|
StoreTestUser: &db_test.StoreTestUser{},
|
|
},
|
|
},
|
|
wantErr: true,
|
|
wantIsErr: ErrInvalidParameter,
|
|
},
|
|
{
|
|
name: "missing-private-id",
|
|
underlying: db,
|
|
args: args{
|
|
resourceWithIder: &db_test.TestScooter{
|
|
StoreTestScooter: &db_test.StoreTestScooter{},
|
|
},
|
|
},
|
|
wantErr: true,
|
|
wantIsErr: ErrInvalidParameter,
|
|
},
|
|
{
|
|
name: "not-an-ider",
|
|
underlying: db,
|
|
args: args{
|
|
resourceWithIder: &db_test.NotIder{},
|
|
},
|
|
wantErr: true,
|
|
wantIsErr: ErrInvalidParameter,
|
|
},
|
|
{
|
|
name: "missing-underlying-db",
|
|
underlying: nil,
|
|
args: args{
|
|
resourceWithIder: user,
|
|
},
|
|
wantErr: true,
|
|
wantIsErr: ErrInvalidParameter,
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
assert, require := assert.New(t), require.New(t)
|
|
rw := &Db{
|
|
underlying: tt.underlying,
|
|
}
|
|
cloner, ok := tt.args.resourceWithIder.(db_test.Cloner)
|
|
require.True(ok)
|
|
cp := cloner.Clone()
|
|
err := rw.LookupById(context.Background(), cp, tt.args.opt...)
|
|
if tt.wantErr {
|
|
require.Error(err)
|
|
require.True(errors.Is(err, tt.wantIsErr))
|
|
return
|
|
}
|
|
require.NoError(err)
|
|
assert.True(proto.Equal(tt.want, cp.(proto.Message)))
|
|
})
|
|
}
|
|
t.Run("not-ptr", func(t *testing.T) {
|
|
u := testUser(t, db, "", "", "")
|
|
rw := &Db{
|
|
underlying: db,
|
|
}
|
|
err := rw.LookupById(context.Background(), *u)
|
|
require.Error(t, err)
|
|
assert.True(t, errors.Is(err, ErrInvalidParameter))
|
|
})
|
|
}
|
|
|
|
func TestDb_GetTicket(t *testing.T) {
|
|
db, _ := TestSetup(t, "postgres")
|
|
type notReplayable struct{}
|
|
tests := []struct {
|
|
name string
|
|
underlying *gorm.DB
|
|
aggregateType interface{}
|
|
wantErr bool
|
|
wantErrIs error
|
|
}{
|
|
{
|
|
name: "simple",
|
|
underlying: db,
|
|
aggregateType: &db_test.TestUser{},
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "not-replayable",
|
|
underlying: db,
|
|
aggregateType: ¬Replayable{},
|
|
wantErr: true,
|
|
wantErrIs: ErrInvalidParameter,
|
|
},
|
|
{
|
|
name: "nil-aggregate-type",
|
|
underlying: db,
|
|
aggregateType: nil,
|
|
wantErr: true,
|
|
wantErrIs: ErrInvalidParameter,
|
|
},
|
|
{
|
|
name: "no-underlying",
|
|
underlying: nil,
|
|
aggregateType: &db_test.TestUser{},
|
|
wantErr: true,
|
|
wantErrIs: ErrInvalidParameter,
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
assert, require := assert.New(t), require.New(t)
|
|
rw := &Db{
|
|
underlying: tt.underlying,
|
|
}
|
|
got, err := rw.GetTicket(tt.aggregateType)
|
|
if tt.wantErr {
|
|
require.Error(err)
|
|
if tt.wantErrIs != nil {
|
|
assert.Truef(errors.Is(err, tt.wantErrIs), "unexpected error type: %s", err.Error())
|
|
}
|
|
return
|
|
}
|
|
require.NoError(err)
|
|
assert.NotEmpty(got.Name)
|
|
assert.NotEmpty(got.Version)
|
|
assert.NotEmpty(got.CreateTime)
|
|
assert.NotEmpty(got.UpdateTime)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestDb_WriteOplogEntryWith(t *testing.T) {
|
|
db, _ := TestSetup(t, "postgres")
|
|
w := Db{underlying: db}
|
|
|
|
ticket, err := w.GetTicket(&db_test.TestUser{})
|
|
require.NoError(t, err)
|
|
|
|
id, err := uuid.GenerateUUID()
|
|
assert.NoError(t, err)
|
|
user, err := db_test.NewTestUser()
|
|
assert.NoError(t, err)
|
|
user.Name = "foo-" + id
|
|
createMsg := oplog.Message{}
|
|
err = w.Create(
|
|
context.Background(),
|
|
user,
|
|
NewOplogMsg(&createMsg),
|
|
)
|
|
require.NoError(t, err)
|
|
metadata := oplog.Metadata{
|
|
"resource-public-id": []string{user.PublicId},
|
|
}
|
|
|
|
type args struct {
|
|
wrapper wrapping.Wrapper
|
|
ticket *store.Ticket
|
|
metadata oplog.Metadata
|
|
msgs []*oplog.Message
|
|
opt []Option
|
|
}
|
|
tests := []struct {
|
|
name string
|
|
underlying *gorm.DB
|
|
args args
|
|
wantErr bool
|
|
wantErrIs error
|
|
wantErrContains string
|
|
}{
|
|
{
|
|
name: "valid",
|
|
underlying: db,
|
|
args: args{
|
|
wrapper: TestWrapper(t),
|
|
ticket: ticket,
|
|
metadata: metadata,
|
|
msgs: []*oplog.Message{&createMsg},
|
|
},
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "valid-multiple",
|
|
underlying: db,
|
|
args: args{
|
|
wrapper: TestWrapper(t),
|
|
ticket: ticket,
|
|
metadata: metadata,
|
|
msgs: []*oplog.Message{&createMsg, &createMsg},
|
|
},
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "missing-ticket",
|
|
underlying: db,
|
|
args: args{
|
|
wrapper: TestWrapper(t),
|
|
ticket: nil,
|
|
metadata: metadata,
|
|
msgs: []*oplog.Message{&createMsg},
|
|
},
|
|
wantErr: true,
|
|
wantErrIs: ErrInvalidParameter,
|
|
},
|
|
{
|
|
name: "missing-db",
|
|
underlying: nil,
|
|
args: args{
|
|
wrapper: TestWrapper(t),
|
|
ticket: ticket,
|
|
metadata: metadata,
|
|
msgs: []*oplog.Message{&createMsg},
|
|
},
|
|
wantErr: true,
|
|
wantErrIs: ErrInvalidParameter,
|
|
},
|
|
{
|
|
name: "missing-wrapper",
|
|
underlying: db,
|
|
args: args{
|
|
wrapper: nil,
|
|
ticket: ticket,
|
|
metadata: metadata,
|
|
msgs: []*oplog.Message{&createMsg},
|
|
},
|
|
wantErr: true,
|
|
wantErrIs: ErrInvalidParameter,
|
|
},
|
|
{
|
|
name: "nil-metadata",
|
|
underlying: db,
|
|
args: args{
|
|
wrapper: TestWrapper(t),
|
|
ticket: ticket,
|
|
metadata: nil,
|
|
msgs: []*oplog.Message{&createMsg},
|
|
},
|
|
wantErr: true,
|
|
wantErrIs: ErrInvalidParameter,
|
|
},
|
|
{
|
|
name: "empty-metadata",
|
|
underlying: db,
|
|
args: args{
|
|
wrapper: TestWrapper(t),
|
|
ticket: ticket,
|
|
metadata: oplog.Metadata{},
|
|
msgs: []*oplog.Message{&createMsg},
|
|
},
|
|
wantErr: true,
|
|
wantErrIs: ErrInvalidParameter,
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
assert, require := assert.New(t), require.New(t)
|
|
rw := &Db{
|
|
underlying: tt.underlying,
|
|
}
|
|
err := rw.WriteOplogEntryWith(context.Background(), tt.args.wrapper, tt.args.ticket, tt.args.metadata, tt.args.msgs, tt.args.opt...)
|
|
if tt.wantErr {
|
|
require.Error(err)
|
|
if tt.wantErrIs != nil {
|
|
assert.Truef(errors.Is(err, tt.wantErrIs), "unexpected error %s", err.Error())
|
|
}
|
|
if tt.wantErrContains != "" {
|
|
assert.Contains(err.Error(), tt.wantErrContains)
|
|
}
|
|
return
|
|
}
|
|
require.NoError(err)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestClear_InputTypes(t *testing.T) {
|
|
type Z struct {
|
|
F string
|
|
}
|
|
|
|
var nilZ *Z
|
|
s := "test-string"
|
|
|
|
type args struct {
|
|
v interface{}
|
|
f []string
|
|
d int
|
|
}
|
|
|
|
var tests = []struct {
|
|
name string
|
|
args args
|
|
want interface{}
|
|
err error
|
|
}{
|
|
{
|
|
name: "nil",
|
|
args: args{
|
|
v: nil,
|
|
f: []string{"field"},
|
|
d: 1,
|
|
},
|
|
err: ErrInvalidParameter,
|
|
},
|
|
{
|
|
name: "string",
|
|
args: args{
|
|
v: "blank",
|
|
f: []string{"field"},
|
|
d: 1,
|
|
},
|
|
err: ErrInvalidParameter,
|
|
},
|
|
{
|
|
name: "pointer-to-nil-struct",
|
|
args: args{
|
|
v: nilZ,
|
|
f: []string{"field"},
|
|
d: 1,
|
|
},
|
|
err: ErrInvalidParameter,
|
|
},
|
|
{
|
|
name: "pointer-to-string",
|
|
args: args{
|
|
v: &s,
|
|
f: []string{"field"},
|
|
d: 1,
|
|
},
|
|
err: ErrInvalidParameter,
|
|
},
|
|
{
|
|
name: "not-pointer",
|
|
args: args{
|
|
v: Z{
|
|
F: "foo",
|
|
},
|
|
f: []string{"field"},
|
|
d: 1,
|
|
},
|
|
err: ErrInvalidParameter,
|
|
},
|
|
{
|
|
name: "map",
|
|
args: args{
|
|
v: map[string]int{
|
|
"A": 31,
|
|
"B": 34,
|
|
},
|
|
f: []string{"field"},
|
|
d: 1,
|
|
},
|
|
err: ErrInvalidParameter,
|
|
},
|
|
{
|
|
name: "pointer-to-struct",
|
|
args: args{
|
|
v: &Z{
|
|
F: "foo",
|
|
},
|
|
f: []string{"field"},
|
|
d: 1,
|
|
},
|
|
want: &Z{
|
|
F: "foo",
|
|
},
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
tt := tt
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
assert, require := assert.New(t), require.New(t)
|
|
input := tt.args.v
|
|
err := Clear(input, tt.args.f, tt.args.d)
|
|
if tt.err != nil {
|
|
assert.Error(err)
|
|
return
|
|
}
|
|
require.NoError(err)
|
|
assert.Equal(tt.want, input)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestClear_Structs(t *testing.T) {
|
|
s := "test-string"
|
|
|
|
type A struct{ F string }
|
|
type B struct{ F *string }
|
|
|
|
type AB struct {
|
|
A A
|
|
B *B
|
|
F string
|
|
|
|
IN io.Reader
|
|
MP map[int]int
|
|
SL []string
|
|
AR [1]int
|
|
CH chan int
|
|
FN func()
|
|
}
|
|
|
|
type AFB struct {
|
|
F A
|
|
B *B
|
|
}
|
|
|
|
type ABF struct {
|
|
A A
|
|
F *B
|
|
}
|
|
|
|
type C struct {
|
|
F string
|
|
NF string
|
|
}
|
|
|
|
type EA struct {
|
|
A
|
|
M string
|
|
NF string
|
|
}
|
|
|
|
type EAP struct {
|
|
*A
|
|
M string
|
|
NF string
|
|
}
|
|
|
|
type DEA struct {
|
|
EA
|
|
F string
|
|
}
|
|
|
|
type DEAP struct {
|
|
*EAP
|
|
F string
|
|
}
|
|
|
|
type args struct {
|
|
v interface{}
|
|
f []string
|
|
d int
|
|
}
|
|
var tests = []struct {
|
|
name string
|
|
args args
|
|
want interface{}
|
|
}{
|
|
{
|
|
name: "blank-A",
|
|
args: args{
|
|
v: &A{},
|
|
f: []string{"F"},
|
|
d: 1,
|
|
},
|
|
want: &A{},
|
|
},
|
|
{
|
|
name: "clear-A",
|
|
args: args{
|
|
v: &A{"clear"},
|
|
f: []string{"F"},
|
|
d: 1,
|
|
},
|
|
want: &A{},
|
|
},
|
|
{
|
|
name: "clear-B",
|
|
args: args{
|
|
v: &B{&s},
|
|
f: []string{"F"},
|
|
d: 1,
|
|
},
|
|
want: &B{},
|
|
},
|
|
{
|
|
name: "clear-C",
|
|
args: args{
|
|
v: &C{"clear", "notclear"},
|
|
f: []string{"F"},
|
|
d: 1,
|
|
},
|
|
want: &C{"", "notclear"},
|
|
},
|
|
{
|
|
name: "shallow-clear-AB",
|
|
args: args{
|
|
v: &AB{
|
|
A: A{"notclear"},
|
|
B: &B{&s},
|
|
F: "clear",
|
|
},
|
|
f: []string{"F"},
|
|
d: 1,
|
|
},
|
|
want: &AB{
|
|
A: A{"notclear"},
|
|
B: &B{&s},
|
|
F: "",
|
|
},
|
|
},
|
|
{
|
|
name: "deep-clear-AB",
|
|
args: args{
|
|
v: &AB{
|
|
A: A{"clear"},
|
|
B: &B{&s},
|
|
F: "clear",
|
|
},
|
|
f: []string{"F"},
|
|
d: 2,
|
|
},
|
|
want: &AB{
|
|
A: A{""},
|
|
B: &B{},
|
|
F: "",
|
|
},
|
|
},
|
|
{
|
|
name: "clear-AFB",
|
|
args: args{
|
|
v: &AFB{
|
|
F: A{"clear"},
|
|
B: &B{&s},
|
|
},
|
|
f: []string{"F"},
|
|
d: 2,
|
|
},
|
|
want: &AFB{
|
|
F: A{""},
|
|
B: &B{},
|
|
},
|
|
},
|
|
{
|
|
name: "clear-ABF",
|
|
args: args{
|
|
v: &ABF{
|
|
A: A{"clear"},
|
|
F: &B{&s},
|
|
},
|
|
f: []string{"F"},
|
|
d: 2,
|
|
},
|
|
want: &ABF{
|
|
A: A{""},
|
|
F: nil,
|
|
},
|
|
},
|
|
{
|
|
name: "embedded-struct",
|
|
args: args{
|
|
v: &EA{
|
|
A: A{"clear"},
|
|
M: "clear",
|
|
NF: "notclear",
|
|
},
|
|
f: []string{"F", "M"},
|
|
d: 2,
|
|
},
|
|
want: &EA{
|
|
A: A{""},
|
|
M: "",
|
|
NF: "notclear",
|
|
},
|
|
},
|
|
{
|
|
name: "embedded-struct-pointer",
|
|
args: args{
|
|
v: &EAP{
|
|
A: &A{"clear"},
|
|
M: "clear",
|
|
NF: "notclear",
|
|
},
|
|
f: []string{"F", "M"},
|
|
d: 2,
|
|
},
|
|
want: &EAP{
|
|
A: &A{""},
|
|
M: "",
|
|
NF: "notclear",
|
|
},
|
|
},
|
|
{
|
|
name: "embedded-struct-pointer-extra-depth",
|
|
args: args{
|
|
v: &EAP{
|
|
A: &A{"clear"},
|
|
M: "clear",
|
|
NF: "notclear",
|
|
},
|
|
f: []string{"F", "M"},
|
|
d: 12,
|
|
},
|
|
want: &EAP{
|
|
A: &A{""},
|
|
M: "",
|
|
NF: "notclear",
|
|
},
|
|
},
|
|
{
|
|
name: "deep-embedded-struct",
|
|
args: args{
|
|
v: &DEA{
|
|
EA: EA{
|
|
A: A{"clear"},
|
|
M: "clear",
|
|
NF: "notclear",
|
|
},
|
|
F: "clear",
|
|
},
|
|
f: []string{"F", "M"},
|
|
d: 3,
|
|
},
|
|
want: &DEA{
|
|
EA: EA{
|
|
A: A{""},
|
|
M: "",
|
|
NF: "notclear",
|
|
},
|
|
F: "",
|
|
},
|
|
},
|
|
{
|
|
name: "deep-embedded-struct-pointer",
|
|
args: args{
|
|
v: &DEAP{
|
|
EAP: &EAP{
|
|
A: &A{"clear"},
|
|
M: "clear",
|
|
NF: "notclear",
|
|
},
|
|
F: "clear",
|
|
},
|
|
f: []string{"F", "M"},
|
|
d: 3,
|
|
},
|
|
want: &DEAP{
|
|
EAP: &EAP{
|
|
A: &A{""},
|
|
M: "",
|
|
NF: "notclear",
|
|
},
|
|
F: "",
|
|
},
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
tt := tt
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
assert, require := assert.New(t), require.New(t)
|
|
input := tt.args.v
|
|
err := Clear(input, tt.args.f, tt.args.d)
|
|
assert.NotEmpty(s)
|
|
require.NoError(err)
|
|
assert.Equal(tt.want, input)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestClear_SetFieldsToNil(t *testing.T) {
|
|
type P struct{ F *string }
|
|
type A struct{ F string }
|
|
|
|
type EA struct {
|
|
A
|
|
M string
|
|
NF string
|
|
}
|
|
|
|
type EAP struct {
|
|
*A
|
|
M string
|
|
NF string
|
|
}
|
|
|
|
type DEA struct {
|
|
EA
|
|
F string
|
|
}
|
|
|
|
type DEAP struct {
|
|
*EAP
|
|
F string
|
|
}
|
|
|
|
type args struct {
|
|
v interface{}
|
|
f []string
|
|
}
|
|
var tests = []struct {
|
|
name string
|
|
args args
|
|
want interface{}
|
|
}{
|
|
{
|
|
name: "dont-panic",
|
|
args: args{
|
|
v: P{},
|
|
f: []string{"F"},
|
|
},
|
|
want: P{},
|
|
},
|
|
{
|
|
name: "deep-embedded-struct",
|
|
args: args{
|
|
v: &DEA{
|
|
EA: EA{
|
|
A: A{"notclear"},
|
|
M: "clear",
|
|
NF: "notclear",
|
|
},
|
|
F: "clear",
|
|
},
|
|
f: []string{"F", "M"},
|
|
},
|
|
want: &DEA{
|
|
EA: EA{
|
|
A: A{"notclear"},
|
|
M: "",
|
|
NF: "notclear",
|
|
},
|
|
F: "",
|
|
},
|
|
},
|
|
{
|
|
name: "deep-embedded-struct-pointer",
|
|
args: args{
|
|
v: &DEAP{
|
|
EAP: &EAP{
|
|
A: &A{"notclear"},
|
|
M: "clear",
|
|
NF: "notclear",
|
|
},
|
|
F: "clear",
|
|
},
|
|
f: []string{"F", "M"},
|
|
},
|
|
want: &DEAP{
|
|
EAP: &EAP{
|
|
A: &A{"notclear"},
|
|
M: "",
|
|
NF: "notclear",
|
|
},
|
|
F: "",
|
|
},
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
tt := tt
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
assert, require := assert.New(t), require.New(t)
|
|
input := tt.args.v
|
|
require.NotPanics(func() {
|
|
setFieldsToNil(input, tt.args.f)
|
|
})
|
|
assert.Equal(tt.want, input)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestDb_oplogMsgsForItems(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
// underlying isn't used at this point, so it can just be nil
|
|
rw := Db{underlying: nil}
|
|
var users []interface{}
|
|
var wantUsrMsgs []*oplog.Message
|
|
for i := 0; i < 5; i++ {
|
|
publicId, err := base62.Random(20)
|
|
require.NoError(t, err)
|
|
u := &db_test.TestUser{StoreTestUser: &db_test.StoreTestUser{PublicId: publicId}}
|
|
users = append(users, u)
|
|
wantUsrMsgs = append(
|
|
wantUsrMsgs,
|
|
&oplog.Message{
|
|
Message: users[i].(proto.Message),
|
|
TypeName: u.TableName(),
|
|
OpType: oplog.OpType_OP_TYPE_CREATE,
|
|
},
|
|
)
|
|
}
|
|
|
|
publicId, err := base62.Random(20)
|
|
require.NoError(t, err)
|
|
mixed := []interface{}{
|
|
&db_test.TestUser{StoreTestUser: &db_test.StoreTestUser{PublicId: publicId}},
|
|
&db_test.TestCar{StoreTestCar: &db_test.StoreTestCar{PublicId: publicId}},
|
|
}
|
|
|
|
type args struct {
|
|
opType OpType
|
|
opts Options
|
|
items []interface{}
|
|
}
|
|
tests := []struct {
|
|
name string
|
|
args args
|
|
want []*oplog.Message
|
|
wantErr bool
|
|
wantIsErr error
|
|
}{
|
|
{
|
|
name: "valid",
|
|
args: args{
|
|
opType: CreateOp,
|
|
items: users,
|
|
},
|
|
wantErr: false,
|
|
want: wantUsrMsgs,
|
|
},
|
|
{
|
|
name: "nil items",
|
|
args: args{
|
|
opType: CreateOp,
|
|
items: nil,
|
|
},
|
|
wantErr: true,
|
|
wantIsErr: ErrInvalidParameter,
|
|
},
|
|
{
|
|
name: "zero items",
|
|
args: args{
|
|
opType: CreateOp,
|
|
items: []interface{}{},
|
|
},
|
|
wantErr: true,
|
|
wantIsErr: ErrInvalidParameter,
|
|
},
|
|
{
|
|
name: "mixed items",
|
|
args: args{
|
|
opType: CreateOp,
|
|
items: mixed,
|
|
},
|
|
wantErr: true,
|
|
wantIsErr: ErrInvalidParameter,
|
|
},
|
|
{
|
|
name: "bad op",
|
|
args: args{
|
|
opType: UnknownOp,
|
|
items: users,
|
|
},
|
|
wantErr: true,
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
assert, require := assert.New(t), require.New(t)
|
|
got, err := rw.oplogMsgsForItems(context.Background(), tt.args.opType, tt.args.opts, tt.args.items)
|
|
if tt.wantErr {
|
|
require.Error(err)
|
|
if tt.wantIsErr != nil {
|
|
assert.Truef(errors.Is(err, tt.wantIsErr), "unexpected error %s", err.Error())
|
|
}
|
|
return
|
|
}
|
|
require.NoError(err)
|
|
assert.Equal(tt.want, got)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestDb_lookupAfterWrite(t *testing.T) {
|
|
t.Parallel()
|
|
db, _ := TestSetup(t, "postgres")
|
|
scooter := testScooter(t, db, "", 0)
|
|
user := testUser(t, db, "", "", "")
|
|
type args struct {
|
|
resourceWithIder interface{}
|
|
opt []Option
|
|
}
|
|
tests := []struct {
|
|
name string
|
|
underlying *gorm.DB
|
|
args args
|
|
wantErr bool
|
|
want proto.Message
|
|
wantIsErr error
|
|
}{
|
|
{
|
|
name: "simple-private-id",
|
|
underlying: db,
|
|
args: args{
|
|
resourceWithIder: scooter,
|
|
opt: []Option{WithLookup(true)},
|
|
},
|
|
wantErr: false,
|
|
want: scooter,
|
|
},
|
|
{
|
|
name: "simple-public-id",
|
|
underlying: db,
|
|
args: args{
|
|
resourceWithIder: user,
|
|
opt: []Option{WithLookup(true)},
|
|
},
|
|
wantErr: false,
|
|
want: user,
|
|
},
|
|
{
|
|
name: "no-lookup-private-id",
|
|
underlying: db,
|
|
args: args{
|
|
resourceWithIder: scooter,
|
|
opt: []Option{WithLookup(false)},
|
|
},
|
|
wantErr: false,
|
|
want: nil,
|
|
},
|
|
{
|
|
name: "no-lookup-public-id",
|
|
underlying: db,
|
|
args: args{
|
|
resourceWithIder: user,
|
|
opt: []Option{WithLookup(false)},
|
|
},
|
|
wantErr: false,
|
|
want: nil,
|
|
},
|
|
{
|
|
name: "not-an-ider",
|
|
underlying: db,
|
|
args: args{
|
|
resourceWithIder: &db_test.NotIder{},
|
|
opt: []Option{WithLookup(true)},
|
|
},
|
|
wantErr: true,
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
assert, require := assert.New(t), require.New(t)
|
|
rw := &Db{
|
|
underlying: tt.underlying,
|
|
}
|
|
cloner, ok := tt.args.resourceWithIder.(db_test.Cloner)
|
|
require.True(ok)
|
|
cp := cloner.Clone()
|
|
err := rw.lookupAfterWrite(context.Background(), cp, tt.args.opt...)
|
|
if tt.wantErr {
|
|
require.Error(err)
|
|
return
|
|
}
|
|
require.NoError(err)
|
|
if tt.want != nil {
|
|
assert.True(proto.Equal(tt.want, cp.(proto.Message)))
|
|
}
|
|
})
|
|
}
|
|
}
|