Files
threshold/internal/oplog/oplog_test.go
T

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

994 lines
30 KiB
Go
Raw Normal View History

2020-08-11 17:25:00 -04:00
// Copyright (c) HashiCorp, Inc.
2020-04-08 14:55:40 -04:00
// SPDX-License-Identifier: BUSL-1.1
2023-02-02 17:15:17 -08:00
2020-04-08 14:55:40 -04:00
package oplog
import (
"context"
"fmt"
"testing"
2022-11-16 17:17:28 -06:00
"github.com/hashicorp/boundary/internal/oplog/oplog_test"
2020-06-10 06:56:46 -04:00
dbassert "github.com/hashicorp/dbassert/gorm"
"github.com/hashicorp/go-dbw"
2020-06-10 06:56:46 -04:00
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
2020-04-08 14:55:40 -04:00
_ "github.com/golang-migrate/migrate/v4/database/postgres"
_ "github.com/golang-migrate/migrate/v4/source/file"
)
// Test_BasicOplog provides some basic unit tests for oplogs
func Test_BasicOplog(t *testing.T) {
testCtx := context.Background()
2022-11-16 17:17:28 -06:00
db, wrapper := setup(testCtx, t)
2020-04-08 14:55:40 -04:00
t.Run("EncryptData/DecryptData/UnmarshalData", func(t *testing.T) {
2020-06-10 06:56:46 -04:00
assert, require := assert.New(t), require.New(t)
2022-11-16 17:17:28 -06:00
keyId, err := wrapper.KeyId(testCtx)
require.NoError(err)
2020-04-08 14:55:40 -04:00
// now let's us optimistic locking via a ticketing system for a serialized oplog
ticketer, err := NewTicketer(testCtx, db, WithAggregateNames(true))
2020-06-10 06:56:46 -04:00
require.NoError(err)
2020-04-08 14:55:40 -04:00
types, err := NewTypeCatalog(testCtx, Type{new(oplog_test.TestUser), "user"})
2020-06-10 06:56:46 -04:00
require.NoError(err)
2020-04-08 14:55:40 -04:00
queue := Queue{Catalog: types}
2020-06-10 06:56:46 -04:00
id := testId(t)
user := testUser(t, db, "foo-"+id, "", "")
2020-04-08 14:55:40 -04:00
err = queue.add(testCtx, user, "user", OpType_OP_TYPE_CREATE)
2020-06-10 06:56:46 -04:00
require.NoError(err)
2020-04-08 14:55:40 -04:00
l, err := NewEntry(
testCtx,
2020-04-08 14:55:40 -04:00
"test-users",
Metadata{
"key-only": nil,
"deployment": []string{"amex"},
"project": []string{"central-info-systems", "local-info-systems"},
},
2022-11-16 17:17:28 -06:00
wrapper,
2020-04-08 14:55:40 -04:00
ticketer,
)
2020-06-10 06:56:46 -04:00
require.NoError(err)
2020-04-08 14:55:40 -04:00
l.Data = queue.Bytes()
err = l.encryptData(context.Background())
2020-06-10 06:56:46 -04:00
require.NoError(err)
2020-04-08 14:55:40 -04:00
err = dbw.New(db).Create(testCtx, &l)
2020-06-10 06:56:46 -04:00
require.NoError(err)
assert.NotNil(l.CreateTime)
assert.NotNil(l.UpdateTime)
2020-04-08 14:55:40 -04:00
entryId := l.Id
var foundEntry Entry
2022-10-25 18:29:38 -05:00
err = dbw.New(db).LookupWhere(testCtx, &foundEntry, "id = ?", []any{entryId})
2020-06-10 06:56:46 -04:00
require.NoError(err)
2022-11-16 17:17:28 -06:00
foundEntry.Wrapper = wrapper
2020-04-08 14:55:40 -04:00
err = foundEntry.DecryptData(context.Background())
2020-06-10 06:56:46 -04:00
require.NoError(err)
2022-11-16 17:17:28 -06:00
require.Equal(keyId, foundEntry.KeyId)
require.Equal("global", foundEntry.ScopeId)
2020-04-08 14:55:40 -04:00
foundUsers, err := foundEntry.UnmarshalData(testCtx, types)
2020-06-10 06:56:46 -04:00
require.NoError(err)
assert.Equal(foundUsers[0].Message.(*oplog_test.TestUser).Name, user.Name)
foundUsers, err = foundEntry.UnmarshalData(testCtx, types)
2020-06-10 06:56:46 -04:00
require.NoError(err)
assert.Equal(foundUsers[0].Message.(*oplog_test.TestUser).Name, user.Name)
2020-04-08 14:55:40 -04:00
})
t.Run("write entry", func(t *testing.T) {
2020-06-10 06:56:46 -04:00
assert, require := assert.New(t), require.New(t)
2020-04-08 14:55:40 -04:00
// now let's us optimistic locking via a ticketing system for a serialized oplog
ticketer, err := NewTicketer(testCtx, db, WithAggregateNames(true))
2020-06-10 06:56:46 -04:00
require.NoError(err)
2020-04-08 14:55:40 -04:00
types, err := NewTypeCatalog(testCtx, Type{new(oplog_test.TestUser), "user"})
2020-06-10 06:56:46 -04:00
require.NoError(err)
2020-04-08 14:55:40 -04:00
queue := Queue{Catalog: types}
2020-06-10 06:56:46 -04:00
id := testId(t)
user := testUser(t, db, "foo-"+id, "", "")
ticket, err := ticketer.GetTicket(testCtx, "default")
2020-06-10 06:56:46 -04:00
require.NoError(err)
2020-04-08 14:55:40 -04:00
err = queue.add(testCtx, user, "user", OpType_OP_TYPE_CREATE)
2020-06-10 06:56:46 -04:00
require.NoError(err)
2020-04-08 14:55:40 -04:00
newLogEntry, err := NewEntry(
testCtx,
2020-04-08 14:55:40 -04:00
"test-users",
Metadata{
"key-only": nil,
"deployment": []string{"amex"},
"project": []string{"central-info-systems", "local-info-systems"},
},
2022-11-16 17:17:28 -06:00
wrapper,
2020-04-08 14:55:40 -04:00
ticketer,
)
2020-06-10 06:56:46 -04:00
require.NoError(err)
2020-04-08 14:55:40 -04:00
newLogEntry.Data = queue.Bytes()
err = newLogEntry.Write(context.Background(), &Writer{db}, ticket)
2020-06-10 06:56:46 -04:00
require.NoError(err)
assert.NotEmpty(newLogEntry.Id)
2020-04-08 14:55:40 -04:00
})
}
// Test_NewEntry provides some basic unit tests for NewEntry
func Test_NewEntry(t *testing.T) {
testCtx := context.Background()
2022-11-16 17:17:28 -06:00
db, wrapper := setup(testCtx, t)
2020-06-10 06:56:46 -04:00
2020-04-08 14:55:40 -04:00
t.Run("valid", func(t *testing.T) {
2020-06-10 06:56:46 -04:00
require := require.New(t)
ticketer, err := NewTicketer(testCtx, db, WithAggregateNames(true))
2020-06-10 06:56:46 -04:00
require.NoError(err)
2022-11-16 17:17:28 -06:00
keyId, err := wrapper.KeyId(testCtx)
require.NoError(err)
2020-04-08 14:55:40 -04:00
2022-11-16 17:17:28 -06:00
entry, err := NewEntry(
testCtx,
2020-04-08 14:55:40 -04:00
"test-users",
Metadata{
"key-only": nil,
"deployment": []string{"amex"},
"project": []string{"central-info-systems", "local-info-systems"},
},
2022-11-16 17:17:28 -06:00
wrapper,
2020-04-08 14:55:40 -04:00
ticketer,
)
2020-06-10 06:56:46 -04:00
require.NoError(err)
2022-11-16 17:17:28 -06:00
require.Equal(keyId, entry.KeyId)
2020-04-08 14:55:40 -04:00
})
t.Run("no metadata success", func(t *testing.T) {
2020-06-10 06:56:46 -04:00
require := require.New(t)
ticketer, err := NewTicketer(testCtx, db, WithAggregateNames(true))
2020-06-10 06:56:46 -04:00
require.NoError(err)
2020-04-08 14:55:40 -04:00
_, err = NewEntry(
testCtx,
2020-04-08 14:55:40 -04:00
"test-users",
nil,
2022-11-16 17:17:28 -06:00
wrapper,
2020-04-08 14:55:40 -04:00
ticketer,
)
2020-06-10 06:56:46 -04:00
require.NoError(err)
2020-04-08 14:55:40 -04:00
})
t.Run("no aggregateName", func(t *testing.T) {
2020-06-10 06:56:46 -04:00
assert, require := assert.New(t), require.New(t)
ticketer, err := NewTicketer(testCtx, db, WithAggregateNames(true))
2020-06-10 06:56:46 -04:00
require.NoError(err)
2020-04-08 14:55:40 -04:00
_, err = NewEntry(
testCtx,
2020-04-08 14:55:40 -04:00
"",
Metadata{
"key-only": nil,
"deployment": []string{"amex"},
"project": []string{"central-info-systems", "local-info-systems"},
},
2022-11-16 17:17:28 -06:00
wrapper,
2020-04-08 14:55:40 -04:00
ticketer,
)
2020-06-10 06:56:46 -04:00
require.Error(err)
assert.Equal("oplog.NewEntry: oplog.(Entry).validate: missing entry aggregate name: parameter violation: error #100", err.Error())
2020-04-08 14:55:40 -04:00
})
2022-11-16 17:17:28 -06:00
t.Run("bad wrapper", func(t *testing.T) {
2020-06-10 06:56:46 -04:00
assert, require := assert.New(t), require.New(t)
ticketer, err := NewTicketer(testCtx, db, WithAggregateNames(true))
2020-06-10 06:56:46 -04:00
require.NoError(err)
2020-04-08 14:55:40 -04:00
_, err = NewEntry(
testCtx,
2020-04-08 14:55:40 -04:00
"test-users",
Metadata{
"key-only": nil,
"deployment": []string{"amex"},
"project": []string{"central-info-systems", "local-info-systems"},
},
nil,
ticketer,
)
2020-06-10 06:56:46 -04:00
require.Error(err)
2022-11-16 17:17:28 -06:00
assert.Equal("oplog.NewEntry: nil wrapper: parameter violation: error #100", err.Error())
2020-04-08 14:55:40 -04:00
})
t.Run("bad ticket", func(t *testing.T) {
2020-06-10 06:56:46 -04:00
assert, require := assert.New(t), require.New(t)
2020-04-08 14:55:40 -04:00
_, err := NewEntry(
testCtx,
2020-04-08 14:55:40 -04:00
"test-users",
Metadata{
"key-only": nil,
"deployment": []string{"amex"},
"project": []string{"central-info-systems", "local-info-systems"},
},
2022-11-16 17:17:28 -06:00
wrapper,
2020-04-08 14:55:40 -04:00
nil,
)
2020-06-10 06:56:46 -04:00
require.Error(err)
assert.Equal("oplog.NewEntry: oplog.(Entry).validate: nil ticketer: parameter violation: error #100", err.Error())
2020-04-08 14:55:40 -04:00
})
}
func Test_UnmarshalData(t *testing.T) {
testCtx := context.Background()
2022-11-16 17:17:28 -06:00
db, wrapper := setup(testCtx, t)
2020-04-08 14:55:40 -04:00
// now let's us optimistic locking via a ticketing system for a serialized oplog
ticketer, err := NewTicketer(testCtx, db, WithAggregateNames(true))
require.NoError(t, err)
2020-04-08 14:55:40 -04:00
types, err := NewTypeCatalog(testCtx, Type{new(oplog_test.TestUser), "user"})
2020-06-10 06:56:46 -04:00
require.NoError(t, err)
2020-04-08 14:55:40 -04:00
2020-06-10 06:56:46 -04:00
id := testId(t)
2020-04-08 14:55:40 -04:00
t.Run("valid", func(t *testing.T) {
2020-06-10 06:56:46 -04:00
assert, require := assert.New(t), require.New(t)
2020-04-08 14:55:40 -04:00
queue := Queue{Catalog: types}
user := oplog_test.TestUser{
Name: "foo-" + id,
}
err = queue.add(testCtx, &user, "user", OpType_OP_TYPE_CREATE)
2020-06-10 06:56:46 -04:00
require.NoError(err)
2020-04-08 14:55:40 -04:00
entry, err := NewEntry(
testCtx,
2020-04-08 14:55:40 -04:00
"test-users",
Metadata{
"key-only": nil,
"deployment": []string{"amex"},
"project": []string{"central-info-systems", "local-info-systems"},
},
2022-11-16 17:17:28 -06:00
wrapper,
2020-04-08 14:55:40 -04:00
ticketer,
)
2020-06-10 06:56:46 -04:00
require.NoError(err)
2020-04-08 14:55:40 -04:00
entry.Data = queue.Bytes()
marshaledUsers, err := entry.UnmarshalData(testCtx, types)
2020-06-10 06:56:46 -04:00
require.NoError(err)
assert.Equal(marshaledUsers[0].Message.(*oplog_test.TestUser).Name, user.Name)
2020-04-08 14:55:40 -04:00
take2marshaledUsers, err := entry.UnmarshalData(testCtx, types)
2020-06-10 06:56:46 -04:00
require.NoError(err)
assert.Equal(take2marshaledUsers[0].Message.(*oplog_test.TestUser).Name, user.Name)
2020-04-08 14:55:40 -04:00
})
t.Run("no data", func(t *testing.T) {
2020-06-10 06:56:46 -04:00
assert, require := assert.New(t), require.New(t)
2020-04-08 14:55:40 -04:00
queue := Queue{Catalog: types}
entry, err := NewEntry(
testCtx,
2020-04-08 14:55:40 -04:00
"test-users",
Metadata{
"key-only": nil,
"deployment": []string{"amex"},
"project": []string{"central-info-systems", "local-info-systems"},
},
2022-11-16 17:17:28 -06:00
wrapper,
2020-04-08 14:55:40 -04:00
ticketer,
)
2020-06-10 06:56:46 -04:00
require.NoError(err)
2020-04-08 14:55:40 -04:00
entry.Data = queue.Bytes()
_, err = entry.UnmarshalData(testCtx, types)
2020-06-10 06:56:46 -04:00
require.Error(err)
assert.Equal("oplog.(Entry).UnmarshalData: missing data: parameter violation: error #100", err.Error())
2020-04-08 14:55:40 -04:00
})
t.Run("nil types", func(t *testing.T) {
2020-06-10 06:56:46 -04:00
assert, require := assert.New(t), require.New(t)
2020-04-08 14:55:40 -04:00
queue := Queue{Catalog: types}
user := oplog_test.TestUser{
Name: "foo-" + id,
}
err = queue.add(testCtx, &user, "user", OpType_OP_TYPE_CREATE)
2020-06-10 06:56:46 -04:00
require.NoError(err)
2020-04-08 14:55:40 -04:00
entry, err := NewEntry(
testCtx,
2020-04-08 14:55:40 -04:00
"test-users",
Metadata{
"key-only": nil,
"deployment": []string{"amex"},
"project": []string{"central-info-systems", "local-info-systems"},
},
2022-11-16 17:17:28 -06:00
wrapper,
2020-04-08 14:55:40 -04:00
ticketer,
)
2020-06-10 06:56:46 -04:00
require.NoError(err)
_, err = entry.UnmarshalData(testCtx, nil)
2020-06-10 06:56:46 -04:00
require.Error(err)
assert.Equal("oplog.(Entry).UnmarshalData: nil type catalog: parameter violation: error #100", err.Error())
2020-04-08 14:55:40 -04:00
})
t.Run("missing type", func(t *testing.T) {
2020-06-10 06:56:46 -04:00
assert, require := assert.New(t), require.New(t)
2020-04-08 14:55:40 -04:00
queue := Queue{Catalog: types}
user := oplog_test.TestUser{
Name: "foo-" + id,
}
err = queue.add(testCtx, &user, "user", OpType_OP_TYPE_CREATE)
2020-06-10 06:56:46 -04:00
require.NoError(err)
2020-04-08 14:55:40 -04:00
entry, err := NewEntry(
testCtx,
2020-04-08 14:55:40 -04:00
"test-users",
Metadata{
"key-only": nil,
"deployment": []string{"amex"},
"project": []string{"central-info-systems", "local-info-systems"},
},
2022-11-16 17:17:28 -06:00
wrapper,
2020-04-08 14:55:40 -04:00
ticketer,
)
2020-06-10 06:56:46 -04:00
require.NoError(err)
2020-04-08 14:55:40 -04:00
entry.Data = queue.Bytes()
types, err := NewTypeCatalog(testCtx, Type{new(oplog_test.TestUser), "not-valid-name"})
2020-06-10 06:56:46 -04:00
require.NoError(err)
_, err = entry.UnmarshalData(testCtx, types)
2020-06-10 06:56:46 -04:00
require.Error(err)
2021-04-08 20:54:32 -04:00
assert.Equal("oplog.(Entry).UnmarshalData: error removing item from queue: oplog.(Queue).Remove: error getting the TypeName: user: oplog.(TypeCatalog).Get: type name not found: integrity violation: error #105", err.Error())
2020-04-08 14:55:40 -04:00
})
}
// Test_Replay provides some basic unit tests for replaying entries
func Test_Replay(t *testing.T) {
testCtx := context.Background()
2022-11-16 17:17:28 -06:00
db, wrapper := setup(testCtx, t)
2020-04-08 14:55:40 -04:00
2020-06-10 06:56:46 -04:00
id := testId(t)
2020-04-08 14:55:40 -04:00
// setup new tables for replay
tableSuffix := "_" + id
writer := Writer{db}
2020-06-10 06:56:46 -04:00
userModel := &oplog_test.TestUser{}
replayUserTable := fmt.Sprintf("%s%s", userModel.TableName(), tableSuffix)
defer func() { require.NoError(t, writer.dropTableIfExists(testCtx, replayUserTable)) }()
2020-04-08 14:55:40 -04:00
ticketer, err := NewTicketer(testCtx, db, WithAggregateNames(true))
2020-06-10 06:56:46 -04:00
require.NoError(t, err)
2020-04-08 14:55:40 -04:00
t.Run("replay:create/update/createitems", func(t *testing.T) {
2020-06-10 06:56:46 -04:00
assert, require := assert.New(t), require.New(t)
ticket, err := ticketer.GetTicket(testCtx, "default")
2020-06-10 06:56:46 -04:00
require.NoError(err)
2020-04-08 14:55:40 -04:00
newLogEntry, err := NewEntry(
testCtx,
2020-04-08 14:55:40 -04:00
"test-users",
Metadata{
"key-only": nil,
"deployment": []string{"amex"},
"project": []string{"central-info-systems", "local-info-systems"},
},
2022-11-16 17:17:28 -06:00
wrapper,
2020-04-08 14:55:40 -04:00
ticketer,
)
2020-06-10 06:56:46 -04:00
require.NoError(err)
2020-04-08 14:55:40 -04:00
tx := dbw.New(db)
2020-08-11 17:25:00 -04:00
loginName := "foo-" + testId(t)
userCreate := testUser(t, db, loginName, "", "")
userSave := oplog_test.TestUser{
Id: userCreate.Id,
Name: userCreate.Name,
2020-08-11 17:25:00 -04:00
Email: loginName + "@hashicorp.com",
2020-04-08 14:55:40 -04:00
}
version := uint32(1)
rowsUpdated, err := tx.Update(testCtx, &userSave, []string{"Email"}, nil, dbw.WithVersion(&version), dbw.WithWhere("name = ?", loginName))
2020-06-10 06:56:46 -04:00
require.NoError(err)
require.Equal(1, rowsUpdated)
2020-04-08 14:55:40 -04:00
userUpdate := oplog_test.TestUser{
Id: userCreate.Id,
PhoneNumber: "867-5309",
2020-04-08 14:55:40 -04:00
}
tx.Update(testCtx, &userUpdate, []string{"PhoneNumber"}, []string{"Name"})
require.NoError(err)
underlyingDB, err := tx.DB().SqlDB(testCtx)
2020-06-10 06:56:46 -04:00
require.NoError(err)
_, dialectName, err := tx.DB().DbType()
require.NoError(err)
dbassert := dbassert.New(t, underlyingDB, dialectName)
2020-06-10 06:56:46 -04:00
dbassert.IsNull(&userUpdate, "Name")
foundCreateUser := testFindUser(t, tx.DB(), userCreate.Id)
2020-06-10 06:56:46 -04:00
require.Equal(foundCreateUser.Id, userCreate.Id)
require.Equal(foundCreateUser.Name, "")
require.Equal(foundCreateUser.PhoneNumber, userUpdate.PhoneNumber)
2020-04-08 14:55:40 -04:00
userCreateItems := &oplog_test.TestUser{
Name: "foo-" + testId(t),
}
require.NoError(dbw.New(db).CreateItems(context.Background(), []*oplog_test.TestUser{userCreateItems}))
err = newLogEntry.WriteEntryWith(context.Background(), &Writer{tx.DB()}, ticket,
2020-06-10 06:56:46 -04:00
&Message{Message: userCreate, TypeName: "user", OpType: OpType_OP_TYPE_CREATE},
&Message{Message: &userSave, TypeName: "user", OpType: OpType_OP_TYPE_UPDATE, FieldMaskPaths: []string{"Email"}, SetToNullPaths: nil, Opts: []dbw.Option{dbw.WithVersion(&version), dbw.WithWhere("name = ?", loginName)}},
2020-06-10 06:56:46 -04:00
&Message{Message: &userSave, TypeName: "user", OpType: OpType_OP_TYPE_UPDATE, FieldMaskPaths: []string{"Name", "Email"}, SetToNullPaths: nil},
&Message{Message: &userUpdate, TypeName: "user", OpType: OpType_OP_TYPE_UPDATE, SetToNullPaths: []string{"Name"}},
&Message{Message: &userUpdate, TypeName: "user", OpType: OpType_OP_TYPE_UPDATE, FieldMaskPaths: nil, SetToNullPaths: []string{"Name"}},
&Message{Message: &userUpdate, TypeName: "user", OpType: OpType_OP_TYPE_UPDATE, FieldMaskPaths: []string{"PhoneNumber"}, SetToNullPaths: []string{"Name"}},
&Message{Message: userCreateItems, TypeName: "user", OpType: OpType_OP_TYPE_CREATE_ITEMS},
2020-04-08 14:55:40 -04:00
)
2020-06-10 06:56:46 -04:00
require.NoError(err)
2020-04-08 14:55:40 -04:00
types, err := NewTypeCatalog(testCtx, Type{new(oplog_test.TestUser), "user"})
2020-06-10 06:56:46 -04:00
require.NoError(err)
2020-04-08 14:55:40 -04:00
var foundEntry Entry
2022-10-25 18:29:38 -05:00
require.NoError(tx.LookupWhere(testCtx, &foundEntry, "id = ?", []any{newLogEntry.Id}))
2022-11-16 17:17:28 -06:00
foundEntry.Wrapper = wrapper
2020-04-08 14:55:40 -04:00
err = foundEntry.DecryptData(context.Background())
2020-06-10 06:56:46 -04:00
require.NoError(err)
2020-04-08 14:55:40 -04:00
err = foundEntry.Replay(context.Background(), &Writer{tx.DB()}, types, tableSuffix)
2020-06-10 06:56:46 -04:00
require.NoError(err)
foundUser := testFindUser(t, tx.DB(), userCreate.Id)
2020-04-08 14:55:40 -04:00
var foundReplayedUser oplog_test.TestUser
2020-06-10 06:56:46 -04:00
foundReplayedUser.Table = foundReplayedUser.TableName() + tableSuffix
2022-10-25 18:29:38 -05:00
require.NoError(tx.LookupWhere(testCtx, &foundReplayedUser, "id = ?", []any{userCreate.Id}))
2020-06-10 06:56:46 -04:00
require.NoError(err)
assert.Equal(foundUser.Id, foundReplayedUser.Id)
assert.Equal(foundUser.Name, foundReplayedUser.Name)
assert.Equal(foundUser.PhoneNumber, foundReplayedUser.PhoneNumber)
assert.Equal(foundReplayedUser.PhoneNumber, "867-5309")
assert.Equal(foundUser.Email, foundReplayedUser.Email)
2020-08-11 17:25:00 -04:00
assert.Equal(foundReplayedUser.Email, loginName+"@hashicorp.com")
foundReplayedUser.Id = 0
2022-10-25 18:29:38 -05:00
require.NoError(tx.LookupWhere(testCtx, &foundReplayedUser, "id = ?", []any{userCreateItems.Id}, dbw.WithDebug(true)))
require.NoError(err)
2020-04-08 14:55:40 -04:00
})
t.Run("replay:delete", func(t *testing.T) {
2020-06-10 06:56:46 -04:00
assert, require := assert.New(t), require.New(t)
2020-04-08 14:55:40 -04:00
// we need to test delete replays now...
tx2, err := dbw.New(db).Begin(testCtx)
require.NoError(err)
defer tx2.Commit(testCtx)
2020-04-08 14:55:40 -04:00
ticket2, err := ticketer.GetTicket(testCtx, "default")
2020-06-10 06:56:46 -04:00
require.NoError(err)
2020-04-08 14:55:40 -04:00
2020-06-10 06:56:46 -04:00
id4 := testId(t)
2020-08-11 17:25:00 -04:00
loginName2 := "foo-" + id4
2020-04-08 14:55:40 -04:00
// create a user that's replayable
userCreate2 := oplog_test.TestUser{
2020-08-11 17:25:00 -04:00
Name: loginName2,
2020-04-08 14:55:40 -04:00
}
err = tx2.Create(testCtx, &userCreate2)
2020-06-10 06:56:46 -04:00
require.NoError(err)
2020-04-08 14:55:40 -04:00
deleteUser2 := oplog_test.TestUser{
Id: userCreate2.Id,
2020-04-08 14:55:40 -04:00
}
_, err = tx2.Delete(testCtx, &deleteUser2)
2020-06-10 06:56:46 -04:00
require.NoError(err)
2020-04-08 14:55:40 -04:00
newLogEntry2, err := NewEntry(
testCtx,
2020-04-08 14:55:40 -04:00
"test-users",
Metadata{
"key-only": nil,
"deployment": []string{"amex"},
"project": []string{"central-info-systems", "local-info-systems"},
},
2022-11-16 17:17:28 -06:00
wrapper,
2020-04-08 14:55:40 -04:00
ticketer,
)
2020-06-10 06:56:46 -04:00
require.NoError(err)
err = newLogEntry2.WriteEntryWith(context.Background(), &Writer{tx2.DB()}, ticket2,
&Message{Message: &userCreate2, TypeName: "user", OpType: OpType_OP_TYPE_CREATE},
&Message{Message: &deleteUser2, TypeName: "user", OpType: OpType_OP_TYPE_DELETE},
2020-04-08 14:55:40 -04:00
)
2020-06-10 06:56:46 -04:00
require.NoError(err)
2020-04-08 14:55:40 -04:00
var foundEntry2 Entry
2022-10-25 18:29:38 -05:00
err = tx2.LookupWhere(testCtx, &foundEntry2, "id = ?", []any{newLogEntry2.Id})
2020-06-10 06:56:46 -04:00
require.NoError(err)
2022-11-16 17:17:28 -06:00
foundEntry2.Wrapper = wrapper
2020-04-08 14:55:40 -04:00
err = foundEntry2.DecryptData(context.Background())
2020-06-10 06:56:46 -04:00
require.NoError(err)
2020-04-08 14:55:40 -04:00
types, err := NewTypeCatalog(testCtx, Type{new(oplog_test.TestUser), "user"})
2020-06-10 06:56:46 -04:00
require.NoError(err)
2020-04-08 14:55:40 -04:00
err = foundEntry2.Replay(context.Background(), &Writer{tx2.DB()}, types, tableSuffix)
2020-06-10 06:56:46 -04:00
require.NoError(err)
2020-04-08 14:55:40 -04:00
var foundUser2 oplog_test.TestUser
2022-10-25 18:29:38 -05:00
err = tx2.LookupWhere(testCtx, &foundUser2, "id = ?", []any{userCreate2.Id})
assert.ErrorIs(err, dbw.ErrRecordNotFound)
2020-04-08 14:55:40 -04:00
var foundReplayedUser2 oplog_test.TestUser
2022-10-25 18:29:38 -05:00
err = tx2.LookupWhere(testCtx, &foundReplayedUser2, "id = ?", []any{userCreate2.Id})
assert.ErrorIs(err, dbw.ErrRecordNotFound)
})
t.Run("replay:deleteitems", func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
// we need to test delete replays now...
tx2, err := dbw.New(db).Begin(testCtx)
require.NoError(err)
defer tx2.Commit(testCtx)
ticket2, err := ticketer.GetTicket(testCtx, "default")
require.NoError(err)
id4 := testId(t)
loginName2 := "foo-" + id4
// create a user that's replayable
userCreate2 := oplog_test.TestUser{
Name: loginName2,
}
err = tx2.Create(testCtx, &userCreate2)
require.NoError(err)
deleteUser2 := oplog_test.TestUser{
Id: userCreate2.Id,
}
_, err = tx2.DeleteItems(testCtx, []*oplog_test.TestUser{&deleteUser2})
require.NoError(err)
newLogEntry2, err := NewEntry(
testCtx,
"test-users",
Metadata{
"key-only": nil,
"deployment": []string{"amex"},
"project": []string{"central-info-systems", "local-info-systems"},
},
2022-11-16 17:17:28 -06:00
wrapper,
ticketer,
)
require.NoError(err)
err = newLogEntry2.WriteEntryWith(context.Background(), &Writer{tx2.DB()}, ticket2,
&Message{Message: &userCreate2, TypeName: "user", OpType: OpType_OP_TYPE_CREATE},
&Message{Message: &deleteUser2, TypeName: "user", OpType: OpType_OP_TYPE_DELETE_ITEMS},
)
require.NoError(err)
var foundEntry2 Entry
2022-10-25 18:29:38 -05:00
err = tx2.LookupWhere(testCtx, &foundEntry2, "id = ?", []any{newLogEntry2.Id})
require.NoError(err)
2022-11-16 17:17:28 -06:00
foundEntry2.Wrapper = wrapper
err = foundEntry2.DecryptData(context.Background())
require.NoError(err)
types, err := NewTypeCatalog(testCtx, Type{new(oplog_test.TestUser), "user"})
require.NoError(err)
err = foundEntry2.Replay(context.Background(), &Writer{tx2.DB()}, types, tableSuffix)
require.NoError(err)
var foundUser2 oplog_test.TestUser
2022-10-25 18:29:38 -05:00
err = tx2.LookupWhere(testCtx, &foundUser2, "id = ?", []any{userCreate2.Id})
assert.ErrorIs(err, dbw.ErrRecordNotFound)
var foundReplayedUser2 oplog_test.TestUser
2022-10-25 18:29:38 -05:00
err = tx2.LookupWhere(testCtx, &foundReplayedUser2, "id = ?", []any{userCreate2.Id})
assert.ErrorIs(err, dbw.ErrRecordNotFound)
2020-04-08 14:55:40 -04:00
})
}
// Test_WriteEntryWith provides unit tests for oplog.WriteEntryWith
func Test_WriteEntryWith(t *testing.T) {
testCtx := context.Background()
2022-11-16 17:17:28 -06:00
db, wrapper := setup(testCtx, t)
2020-04-08 14:55:40 -04:00
2020-06-10 06:56:46 -04:00
id := testId(t)
2020-04-08 14:55:40 -04:00
u := oplog_test.TestUser{
Name: "foo-" + id,
}
t.Log(&u)
2020-06-10 06:56:46 -04:00
id2 := testId(t)
2020-04-08 14:55:40 -04:00
u2 := oplog_test.TestUser{
Name: "foo-" + id2,
}
t.Log(&u2)
ticketer, err := NewTicketer(testCtx, db, WithAggregateNames(true))
2020-06-10 06:56:46 -04:00
require.NoError(t, err)
2020-04-08 14:55:40 -04:00
ticket, err := ticketer.GetTicket(testCtx, "default")
2020-06-10 06:56:46 -04:00
require.NoError(t, err)
2020-04-08 14:55:40 -04:00
t.Run("successful", func(t *testing.T) {
2020-06-10 06:56:46 -04:00
assert, require := assert.New(t), require.New(t)
2020-04-08 14:55:40 -04:00
newLogEntry, err := NewEntry(
testCtx,
2020-04-08 14:55:40 -04:00
"test-users",
Metadata{
"key-only": nil,
"deployment": []string{"amex"},
"project": []string{"central-info-systems", "local-info-systems"},
},
2022-11-16 17:17:28 -06:00
wrapper,
2020-04-08 14:55:40 -04:00
ticketer,
)
2020-06-10 06:56:46 -04:00
require.NoError(err)
err = newLogEntry.WriteEntryWith(context.Background(), &Writer{db}, ticket,
&Message{Message: &u, TypeName: "user", OpType: OpType_OP_TYPE_CREATE},
&Message{Message: &u2, TypeName: "user", OpType: OpType_OP_TYPE_CREATE})
2020-06-10 06:56:46 -04:00
require.NoError(err)
2020-04-08 14:55:40 -04:00
var foundEntry Entry
2022-10-25 18:29:38 -05:00
require.NoError(dbw.New(db).LookupWhere(testCtx, &foundEntry, "id = ?", []any{newLogEntry.Id}))
2020-06-10 06:56:46 -04:00
require.NoError(err)
2022-11-16 17:17:28 -06:00
foundEntry.Wrapper = wrapper
types, err := NewTypeCatalog(testCtx, Type{new(oplog_test.TestUser), "user"})
2020-06-10 06:56:46 -04:00
require.NoError(err)
2020-04-08 14:55:40 -04:00
err = foundEntry.DecryptData(context.Background())
2020-06-10 06:56:46 -04:00
require.NoError(err)
foundUsers, err := foundEntry.UnmarshalData(testCtx, types)
2020-06-10 06:56:46 -04:00
require.NoError(err)
assert.Equal(foundUsers[0].Message.(*oplog_test.TestUser).Name, u.Name)
2020-04-08 14:55:40 -04:00
})
t.Run("nil writer", func(t *testing.T) {
2020-06-10 06:56:46 -04:00
assert, require := assert.New(t), require.New(t)
2020-04-08 14:55:40 -04:00
newLogEntry, err := NewEntry(
testCtx,
2020-04-08 14:55:40 -04:00
"test-users",
Metadata{
"key-only": nil,
"deployment": []string{"amex"},
"project": []string{"central-info-systems", "local-info-systems"},
},
2022-11-16 17:17:28 -06:00
wrapper,
2020-04-08 14:55:40 -04:00
ticketer,
)
2020-06-10 06:56:46 -04:00
require.NoError(err)
2020-04-08 14:55:40 -04:00
err = newLogEntry.WriteEntryWith(context.Background(), nil, ticket,
&Message{Message: &u, TypeName: "user", OpType: OpType_OP_TYPE_CREATE},
&Message{Message: &u2, TypeName: "user", OpType: OpType_OP_TYPE_CREATE})
2020-06-10 06:56:46 -04:00
require.Error(err)
assert.Equal("oplog.(Entry).WriteEntryWith: nil writer: parameter violation: error #100", err.Error())
2020-04-08 14:55:40 -04:00
})
t.Run("nil ticket", func(t *testing.T) {
2020-06-10 06:56:46 -04:00
assert, require := assert.New(t), require.New(t)
2020-04-08 14:55:40 -04:00
newLogEntry, err := NewEntry(
testCtx,
2020-04-08 14:55:40 -04:00
"test-users",
Metadata{
"key-only": nil,
"deployment": []string{"amex"},
"project": []string{"central-info-systems", "local-info-systems"},
},
2022-11-16 17:17:28 -06:00
wrapper,
2020-04-08 14:55:40 -04:00
ticketer,
)
2020-06-10 06:56:46 -04:00
require.NoError(err)
err = newLogEntry.WriteEntryWith(context.Background(), &Writer{db}, nil,
&Message{Message: &u, TypeName: "user", OpType: OpType_OP_TYPE_CREATE},
&Message{Message: &u2, TypeName: "user", OpType: OpType_OP_TYPE_CREATE})
2020-06-10 06:56:46 -04:00
require.Error(err)
assert.Equal("oplog.(Entry).WriteEntryWith: nil ticket: parameter violation: error #100", err.Error())
2020-04-08 14:55:40 -04:00
})
t.Run("nil ticket", func(t *testing.T) {
2020-06-10 06:56:46 -04:00
assert, require := assert.New(t), require.New(t)
2020-04-08 14:55:40 -04:00
newLogEntry, err := NewEntry(
testCtx,
2020-04-08 14:55:40 -04:00
"test-users",
Metadata{
"key-only": nil,
"deployment": []string{"amex"},
"project": []string{"central-info-systems", "local-info-systems"},
},
2022-11-16 17:17:28 -06:00
wrapper,
2020-04-08 14:55:40 -04:00
ticketer,
)
2020-06-10 06:56:46 -04:00
require.NoError(err)
err = newLogEntry.WriteEntryWith(context.Background(), &Writer{db}, ticket, nil)
2020-06-10 06:56:46 -04:00
require.Error(err)
assert.Equal("oplog.(Entry).WriteEntryWith: nil message: parameter violation: error #100", err.Error())
2020-04-08 14:55:40 -04:00
})
}
func TestEntry_WriteEntryWith(t *testing.T) {
2022-11-16 17:17:28 -06:00
assert, require := assert.New(t), require.New(t)
testCtx := context.Background()
2022-11-16 17:17:28 -06:00
db, wrapper := setup(testCtx, t)
db.Debug(true)
// setup new tables for replay
id := testId(t)
tableSuffix := "_" + id
ticketer, err := NewTicketer(testCtx, db, WithAggregateNames(true))
2022-11-16 17:17:28 -06:00
require.NoError(err)
newEntryFn := func() *Entry {
testEntry, err := NewEntry(
testCtx,
"test-users",
Metadata{
"key-only": nil,
"deployment": []string{"amex"},
"project": []string{"central-info-systems", "local-info-systems"},
},
2022-11-16 17:17:28 -06:00
wrapper,
ticketer,
)
2022-11-16 17:17:28 -06:00
require.NoError(err)
return testEntry
}
tests := []struct {
name string
e *Entry
w *Writer
msg *Message
wantErr bool
wantErrContains string
}{
{
name: "on-conflict-columns-do-nothing",
e: newEntryFn(),
w: &Writer{db},
msg: &Message{
Message: testUser(t, db, "foo-"+testId(t), "", ""),
TypeName: "user", OpType: OpType_OP_TYPE_CREATE,
Opts: []dbw.Option{
dbw.WithOnConflict(&dbw.OnConflict{
Target: dbw.Columns{"name"},
Action: dbw.DoNothing(true),
}),
},
},
},
{
name: "on-conflict-columns-update-all",
e: newEntryFn(),
w: &Writer{db},
msg: &Message{
Message: testUser(t, db, "foo-"+testId(t), "", ""),
TypeName: "user", OpType: OpType_OP_TYPE_CREATE,
Opts: []dbw.Option{
dbw.WithOnConflict(&dbw.OnConflict{
Target: dbw.Columns{"name"},
Action: dbw.UpdateAll(true),
}),
},
},
},
{
name: "on-conflict-columns-set-col-value-with-expr",
e: newEntryFn(),
w: &Writer{db},
msg: &Message{
Message: testUser(t, db, "foo-"+testId(t), "", ""),
TypeName: "user", OpType: OpType_OP_TYPE_CREATE,
Opts: []dbw.Option{
dbw.WithOnConflict(&dbw.OnConflict{
Target: dbw.Columns{"name"},
2022-10-25 18:29:38 -05:00
Action: dbw.SetColumnValues(map[string]any{"name": dbw.Expr("NULL")}),
}),
},
},
},
{
name: "on-conflict-columns-set-col-value",
e: newEntryFn(),
w: &Writer{db},
msg: &Message{
Message: testUser(t, db, "foo-"+testId(t), "", ""),
TypeName: "user", OpType: OpType_OP_TYPE_CREATE,
Opts: []dbw.Option{
dbw.WithOnConflict(&dbw.OnConflict{
Target: dbw.Columns{"name"},
2022-10-25 18:29:38 -05:00
Action: dbw.SetColumnValues(map[string]any{"name": testId(t)}),
}),
},
},
},
{
name: "on-conflict-columns-set-col-value",
e: newEntryFn(),
w: &Writer{db},
msg: &Message{
Message: testUser(t, db, "foo-"+testId(t), "", ""),
TypeName: "user", OpType: OpType_OP_TYPE_CREATE,
Opts: []dbw.Option{
dbw.WithOnConflict(&dbw.OnConflict{
Target: dbw.Columns{"name"},
Action: dbw.SetColumns([]string{"email"}),
}),
},
},
},
{
name: "missing-entry",
e: func() *Entry { e := newEntryFn(); e.Entry = nil; return e }(),
w: &Writer{db},
msg: &Message{
Message: testUser(t, db, "foo-"+testId(t), "", ""),
TypeName: "user", OpType: OpType_OP_TYPE_CREATE,
},
wantErr: true,
wantErrContains: "nil entry",
},
{
name: "missing-entry-version",
e: func() *Entry { e := newEntryFn(); e.Entry.Version = ""; return e }(),
w: &Writer{db},
msg: &Message{
Message: testUser(t, db, "foo-"+testId(t), "", ""),
TypeName: "user", OpType: OpType_OP_TYPE_CREATE,
},
wantErr: true,
wantErrContains: "missing entry version",
},
// TODO: jimlambrt (Dec 2021) - we need a way to create a replay table
// that contains the existing named constraints. Once that done, we can
// enable this test and write more. This is just an example of the type
// test we need to create using a constraint target
// {
// name: "on-conflict-column-value",
// e: testEntry,
// w: &Writer{db},
// msg: &Message{
// Message: testUser(t, db, "foo-"+testId(t), "", ""),
// TypeName: "user", OpType: OpType_OP_TYPE_CREATE,
// Opts: []dbw.Option{
// dbw.WithOnConflict(&dbw.OnConflict{
// Target: dbw.Constraint{"oplog_test_user_name_uq"},
// Action: dbw.SetColumnValues(map[string]interface{}{"name": dbw.Expr("NULL")}),
// })},
// },
// },
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
ticket, err := ticketer.GetTicket(testCtx, "default")
require.NoError(err)
err = tt.e.WriteEntryWith(
testCtx,
tt.w,
ticket,
tt.msg,
)
if tt.wantErr {
require.Error(err)
if tt.wantErrContains != "" {
assert.Contains(err.Error(), tt.wantErrContains)
}
return
}
require.NoError(err)
var foundEntry Entry
2022-10-25 18:29:38 -05:00
require.NoError(dbw.New(db).LookupWhere(testCtx, &foundEntry, "id = ?", []any{tt.e.Id}))
require.NoError(err)
2022-11-16 17:17:28 -06:00
foundEntry.Wrapper = wrapper
types, err := NewTypeCatalog(testCtx, Type{new(oplog_test.TestUser), "user"})
require.NoError(err)
err = foundEntry.DecryptData(context.Background())
require.NoError(err)
msgs, err := foundEntry.UnmarshalData(testCtx, types)
require.NoError(err)
require.Equal(1, len(msgs))
entry := msgs[0]
entryUser := entry.Message.(*oplog_test.TestUser)
assert.Equal(entryUser.Name, tt.msg.Message.(*oplog_test.TestUser).Name)
assert.Equal(entry.FieldMaskPaths, tt.msg.FieldMaskPaths)
assert.Equal(entry.SetToNullPaths, tt.msg.SetToNullPaths)
assert.Equal(entry.OpType, tt.msg.OpType)
assert.Equal(entry.TypeName, tt.msg.TypeName)
entryOpts := dbw.GetOpts(entry.Opts...)
testMsgOpts := dbw.GetOpts(tt.msg.Opts...)
assert.Equal(entryOpts, testMsgOpts)
2022-11-16 17:17:28 -06:00
foundEntry.Wrapper = wrapper
err = foundEntry.DecryptData(context.Background())
require.NoError(err)
err = foundEntry.Replay(context.Background(), tt.w, types, tableSuffix)
require.NoError(err)
foundUser := testFindUser(t, tt.w.DB, entryUser.Id)
var foundReplayedUser oplog_test.TestUser
foundReplayedUser.Table = foundReplayedUser.TableName() + tableSuffix
2022-10-25 18:29:38 -05:00
require.NoError(dbw.New(tt.w.DB).LookupWhere(testCtx, &foundReplayedUser, "id = ?", []any{entryUser.Id}))
require.NoError(err)
assert.Equal(foundUser.Id, foundReplayedUser.Id)
assert.Equal(foundUser.Name, foundReplayedUser.Name)
assert.Equal(foundUser.PhoneNumber, foundReplayedUser.PhoneNumber)
assert.Equal(foundUser.Email, foundReplayedUser.Email)
})
}
}
2020-04-08 14:55:40 -04:00
// Test_TicketSerialization provides unit tests for making sure oplog.Tickets properly serialize writes to oplog entries
func Test_TicketSerialization(t *testing.T) {
2020-06-10 06:56:46 -04:00
assert, require := assert.New(t), require.New(t)
testCtx := context.Background()
2022-11-16 17:17:28 -06:00
db, wrapper := setup(testCtx, t)
2020-04-08 14:55:40 -04:00
ticketer, err := NewTicketer(testCtx, db, WithAggregateNames(true))
2020-06-10 06:56:46 -04:00
require.NoError(err)
2020-04-08 14:55:40 -04:00
2020-06-10 06:56:46 -04:00
id := testId(t)
firstTx, err := dbw.New(db).Begin(testCtx)
require.NoError(err)
2020-04-08 14:55:40 -04:00
firstUser := oplog_test.TestUser{
Name: "foo-" + id,
}
err = firstTx.Create(testCtx, &firstUser)
2020-06-10 06:56:46 -04:00
require.NoError(err)
firstTicket, err := ticketer.GetTicket(testCtx, "default")
2020-06-10 06:56:46 -04:00
require.NoError(err)
2020-04-08 14:55:40 -04:00
firstQueue := Queue{}
err = firstQueue.add(testCtx, &firstUser, "user", OpType_OP_TYPE_CREATE)
2020-06-10 06:56:46 -04:00
require.NoError(err)
2020-04-08 14:55:40 -04:00
firstLogEntry, err := NewEntry(
testCtx,
2020-04-08 14:55:40 -04:00
"test-users",
Metadata{
"key-only": nil,
"deployment": []string{"amex"},
"project": []string{"central-info-systems", "local-info-systems"},
},
2022-11-16 17:17:28 -06:00
wrapper,
2020-04-08 14:55:40 -04:00
ticketer,
)
2020-06-10 06:56:46 -04:00
require.NoError(err)
2020-04-08 14:55:40 -04:00
firstLogEntry.Data = firstQueue.Bytes()
2020-06-10 06:56:46 -04:00
id2 := testId(t)
secondTx, err := dbw.New(db).Begin(testCtx)
require.NoError(err)
2020-04-08 14:55:40 -04:00
secondUser := oplog_test.TestUser{
Name: "foo-" + id2,
}
err = secondTx.Create(testCtx, &secondUser)
2020-06-10 06:56:46 -04:00
require.NoError(err)
secondTicket, err := ticketer.GetTicket(testCtx, "default")
2020-06-10 06:56:46 -04:00
require.NoError(err)
2020-04-08 14:55:40 -04:00
secondQueue := Queue{}
err = secondQueue.add(testCtx, &secondUser, "user", OpType_OP_TYPE_CREATE)
2020-06-10 06:56:46 -04:00
require.NoError(err)
2020-04-08 14:55:40 -04:00
secondLogEntry, err := NewEntry(
testCtx,
2020-04-08 14:55:40 -04:00
"foobar",
Metadata{
"key-only": nil,
"deployment": []string{"amex"},
"project": []string{"central-info-systems", "local-info-systems"},
},
2022-11-16 17:17:28 -06:00
wrapper,
2020-04-08 14:55:40 -04:00
ticketer,
)
2020-06-10 06:56:46 -04:00
require.NoError(err)
2020-04-08 14:55:40 -04:00
secondLogEntry.Data = secondQueue.Bytes()
err = secondLogEntry.Write(context.Background(), &Writer{secondTx.DB()}, secondTicket)
2020-06-10 06:56:46 -04:00
require.NoError(err)
assert.NotEmpty(secondLogEntry.Id, 0)
assert.NotNil(secondLogEntry.CreateTime)
assert.NotNil(secondLogEntry.UpdateTime)
err = secondTx.Commit(testCtx)
2020-06-10 06:56:46 -04:00
require.NoError(err)
assert.NotNil(secondLogEntry.Id)
2020-04-08 14:55:40 -04:00
err = firstLogEntry.Write(context.Background(), &Writer{firstTx.DB()}, firstTicket)
2020-04-08 14:55:40 -04:00
if err != nil {
firstTx.Rollback(testCtx)
2020-04-08 14:55:40 -04:00
} else {
firstTx.Commit(testCtx)
2020-04-08 14:55:40 -04:00
t.Error("should have failed to write firstLogEntry")
}
}