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.
boundary/internal/db/read_writer_test.go

3157 lines
78 KiB

package db
import (
"context"
stderrors "errors"
"fmt"
"io/ioutil"
"os"
"strconv"
"strings"
"sync"
"testing"
"time"
"github.com/hashicorp/boundary/internal/db/db_test"
"github.com/hashicorp/boundary/internal/db/timestamp"
"github.com/hashicorp/boundary/internal/errors"
"github.com/hashicorp/boundary/internal/observability/event"
"github.com/hashicorp/boundary/internal/oplog"
"github.com/hashicorp/boundary/internal/oplog/store"
"github.com/hashicorp/go-hclog"
wrapping "github.com/hashicorp/go-kms-wrapping/v2"
"github.com/hashicorp/go-secure-stdlib/base62"
"github.com/hashicorp/go-uuid"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"google.golang.org/protobuf/proto"
"google.golang.org/protobuf/types/known/timestamppb"
)
func TestDb_UpdateUnsetField(t *testing.T) {
conn, _ := TestSetup(t, "postgres")
TestCreateTables(t, conn)
rw := New(conn)
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, "", updatedTu.Email)
assert.Equal(t, "updated", updatedTu.Name)
}
func TestDb_Update(t *testing.T) {
testCtx := context.Background()
conn, _ := TestSetup(t, "postgres")
TestCreateTables(t, conn)
now := &timestamp.Timestamp{Timestamp: timestamppb.Now()}
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: "parameter violation: error #100: dbw.Update: dbw.whereClausesFromOpts: with version option is zero",
},
{
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: `db.Update: column "foo" does not exist: integrity violation: error #1102`,
},
{
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: "parameter violation: error #103: dbw.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: "parameter violation: error #100: dbw.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: "parameter violation: error #100: dbw.Update: missing interface: 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: "parameter violation: error #100: dbw.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 := New(conn)
u := testUser(t, conn, 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.Contains(err.Error(), tt.wantErrMsg)
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, []interface{}{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 := New(conn)
id, err := uuid.GenerateUUID()
assert.NoError(err)
car := testCar(t, conn, "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 := New(conn)
id, err := uuid.GenerateUUID()
require.NoError(err)
user := testUser(t, conn, "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()},
}),
)
conn.Debug(false)
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 := New(conn)
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.Match(errors.T(errors.InvalidParameter), err))
})
t.Run("valid-NewOplogMsg", func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
w := New(conn)
ticket, err := w.GetTicket(testCtx, &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 := New(conn)
id, err := uuid.GenerateUUID()
assert.NoError(err)
user := &testUserWithVet{
PublicId: id,
Name: id,
PhoneNumber: id,
Email: id,
}
err = w.Create(context.Background(), user)
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, conn, "foo-"+id, id, id)
rowsUpdated, err := w.Update(context.Background(), user, []string{"Name"}, nil)
assert.Error(err)
assert.Equal(0, rowsUpdated)
assert.Contains(err.Error(), "db.Update: missing underlying db: parameter violation: error #100")
})
t.Run("no-wrapper-WithOplog", func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
w := New(conn)
id, err := uuid.GenerateUUID()
require.NoError(err)
user := testUser(t, conn, "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.Contains(err.Error(), "oplog validation failed: db.validateOplogArgs: missing wrapper: parameter violation: error #100")
})
t.Run("no-metadata-WithOplog", func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
w := New(conn)
id, err := uuid.GenerateUUID()
require.NoError(err)
user := testUser(t, conn, "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.Contains(err.Error(), "oplog validation failed: db.validateOplogArgs: missing metadata: parameter violation: error #100")
})
t.Run("multi-column", func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
w := New(conn)
scooter := testScooter(t, conn, "", 0)
accessory := testAccessory(t, conn, "test accessory")
scooterAccessory := testScooterAccessory(t, conn, scooter.Id, accessory.AccessoryId)
scooterAccessory.Review = "this is great"
rowsUpdated, err := w.Update(context.Background(), scooterAccessory, []string{"Review"}, nil)
require.NoError(err)
assert.Equal(1, rowsUpdated)
})
}
// 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 stderrors.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 stderrors.New("you cannot change the public id")
}
}
}
if opType == CreateOp {
if u.Id != 0 {
return stderrors.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
conn, _ := TestSetup(t, "postgres")
TestCreateTables(t, conn)
t.Run("simple", func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
w := New(conn)
id, err := uuid.GenerateUUID()
require.NoError(err)
user, err := db_test.NewTestUser()
require.NoError(err)
ts := &timestamp.Timestamp{Timestamp: timestamppb.Now()}
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 := New(conn)
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 := New(conn)
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.Match(errors.T(errors.InvalidParameter), err))
})
t.Run("valid-NewOplogMsg", func(t *testing.T) {
testCtx := context.Background()
assert, require := assert.New(t), require.New(t)
w := New(conn)
ticket, err := w.GetTicket(testCtx, &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},
}
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 := New(conn)
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.Contains(err.Error(), "oplog validation failed: db.validateOplogArgs: missing wrapper: parameter violation: error #100")
})
t.Run("no-metadata-WithOplog", func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
w := New(conn)
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.Contains(err.Error(), "oplog validation failed: db.validateOplogArgs: missing metadata: parameter violation: error #100")
})
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.Contains(err.Error(), "db.Create: missing underlying db: parameter violation: error #100")
})
}
func TestDb_LookupByPublicId(t *testing.T) {
t.Parallel()
conn, _ := TestSetup(t, "postgres")
TestCreateTables(t, conn)
t.Run("simple", func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
w := New(conn)
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.Contains(err.Error(), "db.LookupById: missing underlying db: parameter violation: error #100")
})
t.Run("no-public-id-set", func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
w := New(conn)
foundUser, err := db_test.NewTestUser()
foundUser.PublicId = ""
require.NoError(err)
err = w.LookupByPublicId(context.Background(), foundUser)
require.Error(err)
assert.Contains(err.Error(), "missing primary key: invalid parameter")
})
t.Run("not-found", func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
w := New(conn)
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.True(errors.Match(errors.T(errors.RecordNotFound), err))
})
}
func TestDb_LookupWhere(t *testing.T) {
t.Parallel()
conn, _ := TestSetup(t, "postgres")
TestCreateTables(t, conn)
t.Run("simple", func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
w := New(conn)
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 = ?", []interface{}{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 = ?", []interface{}{1})
require.Error(err)
assert.Equal("db.LookupWhere: missing underlying db: parameter violation: error #100", err.Error())
})
t.Run("not-found", func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
w := New(conn)
id, err := uuid.GenerateUUID()
require.NoError(err)
var foundUser db_test.TestUser
err = w.LookupWhere(context.Background(), &foundUser, "public_id = ?", []interface{}{id})
require.Error(err)
assert.True(errors.Match(errors.T(errors.RecordNotFound), err))
})
t.Run("bad-where", func(t *testing.T) {
require := require.New(t)
w := New(conn)
id, err := uuid.GenerateUUID()
require.NoError(err)
var foundUser db_test.TestUser
err = w.LookupWhere(context.Background(), &foundUser, "? = ?", []interface{}{id})
require.Error(err)
})
}
// TestDb_LookupNotFoundEvent validates that not found error events are suppressed when
// LookupById and LookupWhere are called.
func TestDb_LookupNotFoundEvent(t *testing.T) {
conn, _ := TestSetup(t, "postgres")
TestCreateTables(t, conn)
c := event.TestEventerConfig(t, "TestDb_LookupNotFoundEvent")
testLock := &sync.Mutex{}
testLogger := hclog.New(&hclog.LoggerOptions{
Mutex: testLock,
Name: "test",
})
e, err := event.NewEventer(testLogger, testLock, "TestDb_LookupNotFoundEvent", c.EventerConfig)
require.NoError(t, err)
info := &event.RequestInfo{Id: "867-5309", EventId: "411"}
ctx, err := event.NewEventerContext(context.Background(), e)
require.NoError(t, err)
ctx, err = event.NewRequestInfoContext(ctx, info)
require.NoError(t, err)
t.Run("lookup-where", func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
w := New(conn)
id, err := uuid.GenerateUUID()
require.NoError(err)
var foundUser db_test.TestUser
err = w.LookupWhere(ctx, &foundUser, "public_id = ?", []interface{}{id})
require.Error(err)
assert.True(errors.Match(errors.T(errors.RecordNotFound), err))
defer func() { _ = os.WriteFile(c.AllEvents.Name(), nil, 0o666) }()
b, err := ioutil.ReadFile(c.AllEvents.Name())
require.NoError(err)
// No events should have been written with the above error
assert.Len(b, 0)
})
t.Run("lookup-by-id", func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
w := New(conn)
id, err := uuid.GenerateUUID()
require.NoError(err)
foundUser := db_test.TestUser{
StoreTestUser: &db_test.StoreTestUser{
PublicId: id,
},
}
err = w.LookupById(ctx, &foundUser)
require.Error(err)
assert.True(errors.Match(errors.T(errors.RecordNotFound), err))
defer func() { _ = os.WriteFile(c.AllEvents.Name(), nil, 0o666) }()
b, err := ioutil.ReadFile(c.AllEvents.Name())
require.NoError(err)
// No events should have been written with the above error
assert.Len(b, 0)
})
}
func TestDb_SearchWhere(t *testing.T) {
t.Parallel()
conn, _ := TestSetup(t, "postgres")
TestCreateTables(t, conn)
knownUser := testUser(t, conn, "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: New(conn),
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: "no-where",
db: New(conn),
createCnt: 10,
args: args{
opt: []Option{WithLimit(10)},
},
wantCnt: 10,
wantErr: false,
},
{
name: "custom-limit",
db: New(conn),
createCnt: 10,
args: args{
where: "1=1",
opt: []Option{WithLimit(3)},
},
wantCnt: 3,
wantErr: false,
},
{
name: "simple",
db: New(conn),
createCnt: 1,
args: args{
where: "public_id = ?",
arg: []interface{}{knownUser.PublicId},
opt: []Option{WithLimit(3)},
},
wantCnt: 1,
wantErr: false,
},
{
name: "no args",
db: New(conn),
createCnt: 1,
args: args{
where: fmt.Sprintf("public_id = '%v'", knownUser.PublicId),
opt: []Option{WithLimit(3)},
},
wantCnt: 1,
wantErr: false,
},
{
name: "no where, but with args",
db: New(conn),
createCnt: 1,
args: args{
arg: []interface{}{knownUser.PublicId},
opt: []Option{WithLimit(3)},
},
wantErr: true,
},
{
name: "not-found",
db: New(conn),
createCnt: 1,
args: args{
where: "public_id = ?",
arg: []interface{}{"bad-id"},
opt: []Option{WithLimit(3)},
},
wantCnt: 0,
wantErr: false,
},
{
name: "bad-where",
db: New(conn),
createCnt: 1,
args: args{
where: "bad_column_name = ?",
arg: []interface{}{knownUser.PublicId},
opt: []Option{WithLimit(3)},
},
wantCnt: 0,
wantErr: true,
},
{
name: "nil-underlying",
db: New(nil),
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, conn, tt.name+strconv.Itoa(i), "", ""))
}
assert.Equal(tt.createCnt, len(testUsers))
var foundUsers []*db_test.TestUser
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_Exec(t *testing.T) {
t.Parallel()
t.Run("update", func(t *testing.T) {
conn, _ := TestSetup(t, "postgres")
TestCreateTables(t, conn)
require := require.New(t)
w := New(conn)
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(context.Background(), "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()
ctx := context.TODO()
conn, _ := TestSetup(t, "postgres")
TestCreateTables(t, conn)
t.Run("valid-with-10-retries", func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
w := New(conn)
attempts := 0
got, err := w.DoTx(context.Background(), 10, ExpBackoff{},
func(Reader, Writer) error {
attempts += 1
if attempts < 9 {
return errors.E(ctx, errors.WithCode(errors.TicketAlreadyRedeemed))
}
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 := New(conn)
attempts := 0
got, err := w.DoTx(context.Background(), 1, ExpBackoff{},
func(Reader, Writer) error {
attempts += 1
if attempts < 2 {
return errors.E(ctx, errors.WithCode(errors.TicketAlreadyRedeemed))
}
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 := New(conn)
attempts := 0
got, err := w.DoTx(context.Background(), 3, ExpBackoff{},
func(Reader, Writer) error {
attempts += 1
if attempts < 3 {
return errors.E(ctx, errors.WithCode(errors.TicketAlreadyRedeemed))
}
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 := New(conn)
attempts := 0
got, err := w.DoTx(context.Background(), 4, ExpBackoff{},
func(Reader, Writer) error {
attempts += 1
if attempts < 4 {
return errors.E(ctx, errors.WithCode(errors.TicketAlreadyRedeemed))
}
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 := New(conn)
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{}
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("db.DoTx: missing underlying db: parameter violation: error #100", err.Error())
})
t.Run("not-a-retry-err", func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
w := New(conn)
got, err := w.DoTx(context.Background(), 1, ExpBackoff{}, func(Reader, Writer) error { return stderrors.New("not a retry error") })
require.Error(err)
assert.Equal(RetryInfo{}, got)
assert.False(errors.Match(errors.T(errors.TicketAlreadyRedeemed), err))
})
t.Run("too-many-retries", func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
w := New(conn)
attempts := 0
got, err := w.DoTx(context.Background(), 2, ExpBackoff{}, func(Reader, Writer) error {
attempts += 1
return errors.E(ctx, errors.WithCode(errors.TicketAlreadyRedeemed))
})
require.Error(err)
assert.Equal(3, got.Retries)
assert.Equal("db.DoTx: Too many retries: 3 of 3: db transaction issue: error #1103", err.Error())
})
t.Run("updating-good-bad-good", func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
rw := New(conn)
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) {
conn, _ := TestSetup(t, "postgres")
TestCreateTables(t, conn)
newUser := func() *db_test.TestUser {
w := New(conn)
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},
}
}
// seed some test users, so we won't just happen to get a false positive
// with only 1 entry in the db
for i := 0; i < 1000; i++ {
_ = newUser()
}
type args struct {
i *db_test.TestUser
opt []Option
metadata func(publicId string) oplog.Metadata
}
tests := []struct {
name string
underlying *DB
wrapper wrapping.Wrapper
args args
want int
wantOplog bool
wantFound bool
wantErr bool
wantErrIs errors.Code
}{
{
name: "simple-no-oplog",
underlying: conn,
wrapper: TestWrapper(t),
args: args{
i: newUser(),
},
want: 1,
wantErr: false,
},
{
name: "with-where-no-delete",
underlying: conn,
wrapper: TestWrapper(t),
args: args{
i: newUser(),
opt: []Option{WithWhere("1 = ?", 2)},
},
wantFound: true,
want: 0,
wantErr: false,
},
{
name: "with-where-and-delete",
underlying: conn,
wrapper: TestWrapper(t),
args: args{
i: newUser(),
opt: []Option{WithWhere("1 = ?", 1)},
},
want: 1,
wantErr: false,
},
{
name: "valid-with-oplog",
underlying: conn,
wrapper: TestWrapper(t),
args: args{
i: newUser(),
metadata: newMetadata,
},
wantOplog: true,
want: 1,
wantErr: false,
},
{
name: "nil-wrapper",
underlying: conn,
wrapper: nil,
args: args{
i: newUser(),
metadata: newMetadata,
},
wantOplog: true,
want: 0,
wantErr: true,
wantErrIs: errors.InvalidParameter,
},
{
name: "nil-metadata",
underlying: conn,
wrapper: nil,
args: args{
i: newUser(),
metadata: func(string) oplog.Metadata { return nil },
},
wantOplog: true,
want: 0,
wantErr: true,
wantErrIs: errors.InvalidParameter,
},
{
name: "nil-underlying",
underlying: nil,
wrapper: TestWrapper(t),
args: args{
i: newUser(),
},
want: 0,
wantErr: true,
wantErrIs: errors.InvalidParameter,
},
{
name: "not-found",
underlying: conn,
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 := New(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)
assert.Truef(errors.Match(errors.T(tt.wantErrIs), err), "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)
if tt.wantFound {
assert.NoError(err)
assert.Equal(tt.args.i.PublicId, foundUser.PublicId)
return
}
assert.Error(err)
assert.True(errors.Match(errors.T(errors.RecordNotFound), 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 := New(conn)
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.Match(errors.T(errors.InvalidParameter), err))
})
t.Run("valid-NewOplogMsg", func(t *testing.T) {
testCtx := context.Background()
assert, require := assert.New(t), require.New(t)
w := New(conn)
ticket, err := w.GetTicket(testCtx, &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.Match(errors.T(errors.RecordNotFound), err))
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)
})
t.Run("multi-column", func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
w := New(conn)
scooter := testScooter(t, conn, "", 0)
accessory := testAccessory(t, conn, "test accessory")
scooterAccessory := testScooterAccessory(t, conn, scooter.Id, accessory.AccessoryId)
rowsDeleted, err := w.Delete(context.Background(), scooterAccessory)
require.NoError(err)
assert.Equal(1, rowsDeleted)
})
}
}
func TestDb_ScanRows(t *testing.T) {
t.Parallel()
conn, _ := TestSetup(t, "postgres")
TestCreateTables(t, conn)
t.Run("valid", func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
w := New(conn)
user, err := db_test.NewTestUser()
require.NoError(err)
err = w.Create(context.Background(), user)
require.NoError(err)
assert.NotEmpty(user.Id)
where := "select * from db_test_user where name in (?, ?)"
rows, err := w.Query(context.Background(), where, []interface{}{"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(context.Background(), rows, &u)
require.NoError(err)
assert.Equal(user.PublicId, u.PublicId)
}
})
}
func TestDb_Query(t *testing.T) {
t.Parallel()
conn, _ := TestSetup(t, "postgres")
TestCreateTables(t, conn)
t.Run("valid", func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
rw := New(conn)
user, err := db_test.NewTestUser()
user.Name = "alice"
require.NoError(err)
err = rw.Create(context.Background(), user)
require.NoError(err)
assert.NotEmpty(user.Id)
assert.Equal("alice", user.Name)
where := "select * from db_test_user where name in (?, ?)"
rows, err := rw.Query(context.Background(), where, []interface{}{"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 = rw.ScanRows(context.Background(), rows, &u)
require.NoError(err)
assert.Equal(user.PublicId, u.PublicId)
}
})
}
func TestDb_CreateItems(t *testing.T) {
db, _ := TestSetup(t, "postgres")
TestCreateTables(t, db)
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 *DB
args args
wantOplogId string
wantOplogMsgs bool
wantErr bool
wantErrIs errors.Code
}{
{
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: errors.InvalidParameter,
wantErr: true,
},
{
name: "mixed items",
underlying: db,
args: args{
createItems: createMixedFn(),
},
wantErr: true,
wantErrIs: errors.InvalidParameter,
},
{
name: "bad oplog opt: nil metadata",
underlying: nil,
args: args{
createItems: createFn(),
opt: []Option{
WithOplog(
TestWrapper(t),
nil,
),
},
},
wantErr: true,
wantErrIs: errors.InvalidParameter,
},
{
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: errors.InvalidParameter,
},
{
name: "bad opt: WithLookup",
underlying: nil,
args: args{
createItems: createFn(),
opt: []Option{WithLookup(true)},
},
wantErr: true,
wantErrIs: errors.InvalidParameter,
},
{
name: "nil underlying",
underlying: nil,
args: args{
createItems: createFn(),
},
wantErr: true,
wantErrIs: errors.InvalidParameter,
},
{
name: "empty items",
underlying: db,
args: args{
createItems: []interface{}{},
},
wantErr: true,
wantErrIs: errors.InvalidParameter,
},
{
name: "nil items",
underlying: db,
args: args{
createItems: nil,
},
wantErr: true,
wantErrIs: errors.InvalidParameter,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
rw := New(tt.underlying)
err := rw.CreateItems(context.Background(), tt.args.createItems, tt.args.opt...)
if tt.wantErr {
require.Error(err)
assert.Truef(errors.Match(errors.T(tt.wantErrIs), err), "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")
TestCreateTables(t, db)
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 *DB
args args
wantRowsDeleted int
wantOplogId string
wantOplogMsgs bool
wantErr bool
wantErrIs errors.Code
}{
{
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: errors.InvalidParameter,
},
{
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: errors.InvalidParameter,
},
{
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: errors.InvalidParameter,
},
{
name: "bad opt: WithLookup",
underlying: nil,
args: args{
deleteItems: createFn(),
opt: []Option{WithLookup(true)},
},
wantErr: true,
wantErrIs: errors.InvalidParameter,
},
{
name: "nil underlying",
underlying: nil,
args: args{
deleteItems: createFn(),
},
wantErr: true,
wantErrIs: errors.InvalidParameter,
},
{
name: "empty items",
underlying: db,
args: args{
deleteItems: []interface{}{},
},
wantErr: true,
wantErrIs: errors.InvalidParameter,
},
{
name: "nil items",
underlying: db,
args: args{
deleteItems: nil,
},
wantErr: true,
wantErrIs: errors.InvalidParameter,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
rw := New(tt.underlying)
rowsDeleted, err := rw.DeleteItems(context.Background(), tt.args.deleteItems, tt.args.opt...)
if tt.wantErr {
require.Error(err)
assert.Truef(errors.Match(errors.T(tt.wantErrIs), err), "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.Match(errors.T(errors.RecordNotFound), err), "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 *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 {
rw := New(conn)
err := rw.Create(context.Background(), u)
require.NoError(err)
}
return u
}
func testCar(t *testing.T, conn *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 {
rw := New(conn)
err := rw.Create(context.Background(), c)
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 *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 {
rw := New(conn)
err := rw.Create(context.Background(), u)
require.NoError(err)
}
return u
}
func testAccessory(t *testing.T, conn *DB, description string) *db_test.TestAccessory {
t.Helper()
require := require.New(t)
a, err := db_test.NewTestAccessory(description)
require.NoError(err)
if conn != nil {
rw := New(conn)
rw.Create(context.Background(), a)
require.NoError(err)
}
return a
}
func testScooterAccessory(t *testing.T, conn *DB, scooterId, accessoryId uint32) *db_test.TestScooterAccessory {
t.Helper()
require := require.New(t)
a, err := db_test.NewTestScooterAccessory(scooterId, accessoryId)
require.NoError(err)
if conn != nil {
rw := New(conn)
rw.Create(context.Background(), a)
require.NoError(err)
}
return a
}
func TestDb_LookupById(t *testing.T) {
t.Parallel()
conn, _ := TestSetup(t, "postgres")
TestCreateTables(t, conn)
scooter := testScooter(t, conn, "", 0)
user := testUser(t, conn, "", "", "")
accessory := testAccessory(t, conn, "test accessory")
scooterAccessory := testScooterAccessory(t, conn, scooter.Id, accessory.AccessoryId)
type args struct {
resource interface{}
opt []Option
}
tests := []struct {
name string
underlying *DB
args args
wantErr bool
want proto.Message
wantIsErr errors.Code
}{
{
name: "simple-private-id",
underlying: conn,
args: args{
resource: scooter,
},
wantErr: false,
want: scooter,
},
{
name: "simple-public-id",
underlying: conn,
args: args{
resource: user,
},
wantErr: false,
want: user,
},
{
name: "simple-non-public-non-private-id",
underlying: conn,
args: args{
resource: accessory,
},
wantErr: false,
want: accessory,
},
{
name: "compond",
underlying: conn,
args: args{
resource: scooterAccessory,
},
wantErr: false,
want: scooterAccessory,
},
{
name: "compond-with-zero-value-pk",
underlying: conn,
args: args{
resource: func() interface{} {
cp := scooterAccessory.Clone()
cp.(*db_test.TestScooterAccessory).ScooterId = 0
return cp
}(),
},
wantErr: true,
wantIsErr: errors.InvalidParameter,
},
{
name: "missing-public-id",
underlying: conn,
args: args{
resource: &db_test.TestUser{
StoreTestUser: &db_test.StoreTestUser{},
},
},
wantErr: true,
wantIsErr: errors.InvalidParameter,
},
{
name: "missing-private-id",
underlying: conn,
args: args{
resource: &db_test.TestScooter{
StoreTestScooter: &db_test.StoreTestScooter{},
},
},
wantErr: true,
wantIsErr: errors.InvalidParameter,
},
{
name: "not-an-ider",
underlying: conn,
args: args{
resource: &db_test.NotIder{},
},
wantErr: true,
wantIsErr: errors.InvalidParameter,
},
{
name: "missing-underlying-db",
underlying: nil,
args: args{
resource: user,
},
wantErr: true,
wantIsErr: errors.InvalidParameter,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
rw := New(tt.underlying)
cloner, ok := tt.args.resource.(db_test.Cloner)
require.True(ok)
cp := cloner.Clone()
err := rw.LookupById(context.Background(), cp, tt.args.opt...)
if tt.wantErr {
require.Error(err)
assert.Truef(errors.Match(errors.T(tt.wantIsErr), err), "want err code: %q got: %q", tt.wantIsErr, err)
return
}
require.NoError(err)
assert.True(proto.Equal(tt.want, cp.(proto.Message)))
})
}
t.Run("not-ptr", func(t *testing.T) {
u := testUser(t, conn, "", "", "")
rw := New(conn)
err := rw.LookupById(context.Background(), *u)
require.Error(t, err)
assert.True(t, errors.Match(errors.T(errors.InvalidParameter), err))
})
}
func TestDb_GetTicket(t *testing.T) {
testCtx := context.Background()
db, _ := TestSetup(t, "postgres")
TestCreateTables(t, db)
type notReplayable struct{}
tests := []struct {
name string
underlying *DB
aggregateType interface{}
wantErr bool
wantErrIs errors.Code
}{
{
name: "simple",
underlying: db,
aggregateType: &db_test.TestUser{},
wantErr: false,
},
{
name: "not-replayable",
underlying: db,
aggregateType: &notReplayable{},
wantErr: true,
wantErrIs: errors.InvalidParameter,
},
{
name: "nil-aggregate-type",
underlying: db,
aggregateType: nil,
wantErr: true,
wantErrIs: errors.InvalidParameter,
},
{
name: "no-underlying",
underlying: nil,
aggregateType: &db_test.TestUser{},
wantErr: true,
wantErrIs: errors.InvalidParameter,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
rw := New(tt.underlying)
got, err := rw.GetTicket(testCtx, tt.aggregateType)
if tt.wantErr {
require.Error(err)
assert.Truef(errors.Match(errors.T(tt.wantErrIs), err), "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) {
conn, _ := TestSetup(t, "postgres")
TestCreateTables(t, conn)
w := New(conn)
testCtx := context.Background()
ticket, err := w.GetTicket(testCtx, &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 *DB
args args
wantErr bool
wantErrIs errors.Code
wantErrContains string
}{
{
name: "valid",
underlying: conn,
args: args{
wrapper: TestWrapper(t),
ticket: ticket,
metadata: metadata,
msgs: []*oplog.Message{&createMsg},
},
wantErr: false,
},
{
name: "valid-multiple",
underlying: conn,
args: args{
wrapper: TestWrapper(t),
ticket: ticket,
metadata: metadata,
msgs: []*oplog.Message{&createMsg, &createMsg},
},
wantErr: false,
},
{
name: "missing-ticket",
underlying: conn,
args: args{
wrapper: TestWrapper(t),
ticket: nil,
metadata: metadata,
msgs: []*oplog.Message{&createMsg},
},
wantErr: true,
wantErrIs: errors.InvalidParameter,
},
{
name: "missing-db",
underlying: nil,
args: args{
wrapper: TestWrapper(t),
ticket: ticket,
metadata: metadata,
msgs: []*oplog.Message{&createMsg},
},
wantErr: true,
wantErrIs: errors.InvalidParameter,
},
{
name: "missing-wrapper",
underlying: conn,
args: args{
wrapper: nil,
ticket: ticket,
metadata: metadata,
msgs: []*oplog.Message{&createMsg},
},
wantErr: true,
wantErrIs: errors.InvalidParameter,
},
{
name: "nil-metadata",
underlying: conn,
args: args{
wrapper: TestWrapper(t),
ticket: ticket,
metadata: nil,
msgs: []*oplog.Message{&createMsg},
},
wantErr: true,
wantErrIs: errors.InvalidParameter,
},
{
name: "empty-metadata",
underlying: conn,
args: args{
wrapper: TestWrapper(t),
ticket: ticket,
metadata: oplog.Metadata{},
msgs: []*oplog.Message{&createMsg},
},
wantErr: true,
wantErrIs: errors.InvalidParameter,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
rw := New(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)
assert.Truef(errors.Match(errors.T(tt.wantErrIs), err), "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
// }
// tests := []struct {
// name string
// args args
// want interface{}
// err errors.Code
// }{
// {
// name: "nil",
// args: args{
// v: nil,
// f: []string{"field"},
// d: 1,
// },
// err: errors.InvalidParameter,
// },
// {
// name: "string",
// args: args{
// v: "blank",
// f: []string{"field"},
// d: 1,
// },
// err: errors.InvalidParameter,
// },
// {
// name: "pointer-to-nil-struct",
// args: args{
// v: nilZ,
// f: []string{"field"},
// d: 1,
// },
// err: errors.InvalidParameter,
// },
// {
// name: "pointer-to-string",
// args: args{
// v: &s,
// f: []string{"field"},
// d: 1,
// },
// err: errors.InvalidParameter,
// },
// {
// name: "not-pointer",
// args: args{
// v: Z{
// F: "foo",
// },
// f: []string{"field"},
// d: 1,
// },
// err: errors.InvalidParameter,
// },
// {
// name: "map",
// args: args{
// v: map[string]int{
// "A": 31,
// "B": 34,
// },
// f: []string{"field"},
// d: 1,
// },
// err: errors.InvalidParameter,
// },
// {
// 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 != 0 {
// assert.True(errors.Match(errors.T(tt.err), 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
// }
// 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
// }
// 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 errors.Code
}{
{
name: "valid",
args: args{
opType: CreateOp,
items: users,
},
wantErr: false,
want: wantUsrMsgs,
},
{
name: "nil items",
args: args{
opType: CreateOp,
items: nil,
},
wantErr: true,
wantIsErr: errors.InvalidParameter,
},
{
name: "zero items",
args: args{
opType: CreateOp,
items: []interface{}{},
},
wantErr: true,
wantIsErr: errors.InvalidParameter,
},
{
name: "mixed items",
args: args{
opType: CreateOp,
items: mixed,
},
wantErr: true,
wantIsErr: errors.InvalidParameter,
},
{
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)
assert.Truef(errors.Match(errors.T(tt.wantIsErr), err), "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")
// TestCreateTables(t, db)
// scooter := testScooter(t, db, "", 0)
// user := testUser(t, db, "", "", "")
// type args struct {
// resourceWithIder interface{}
// opt []Option
// }
// tests := []struct {
// name string
// underlying *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 := New(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)))
// }
// })
// }
// }