feat(orm): add mock hooks (#11135)

* feat(orm): add mock hooks

* add hooks

* add tests

* update docs

* Update orm/testing/ormmocks/docs.go

Co-authored-by: Tyler <48813565+technicallyty@users.noreply.github.com>

* add Backend.WithHooks method

Co-authored-by: Tyler <48813565+technicallyty@users.noreply.github.com>
This commit is contained in:
Aaron Craelius 2022-02-08 11:56:53 -05:00 committed by GitHub
parent 4addb7368c
commit 77ac8fa378
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 190 additions and 6 deletions

View File

@ -151,6 +151,7 @@ mocks: $(MOCKS_DIR)
$(mockgen_cmd) -source=types/router.go -package mocks -destination tests/mocks/types_router.go
$(mockgen_cmd) -package mocks -destination tests/mocks/grpc_server.go github.com/gogo/protobuf/grpc Server
$(mockgen_cmd) -package mocks -destination tests/mocks/tendermint_tendermint_libs_log_DB.go github.com/tendermint/tendermint/libs/log Logger
$(mockgen_cmd) -source=orm/model/ormtable/hooks.go -package ormmocks -destination orm/testing/ormmocks/hooks.go
.PHONY: mocks
$(MOCKS_DIR):

View File

@ -6,6 +6,7 @@ require (
github.com/cosmos/cosmos-proto v1.0.0-alpha7
github.com/cosmos/cosmos-sdk/api v0.1.0-alpha4
github.com/cosmos/cosmos-sdk/errors v1.0.0-beta.2
github.com/golang/mock v1.6.0
github.com/iancoleman/strcase v0.2.0
github.com/stretchr/testify v1.7.0
github.com/tendermint/tm-db v0.6.6

View File

@ -63,7 +63,10 @@ github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeME
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/mock v1.1.1 h1:G5FRp8JnTd7RQH5kemVNlMeyXQAztQ3mOWV95KxsXH8=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc=
github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
@ -154,6 +157,7 @@ github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljT
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
go.etcd.io/bbolt v1.3.6 h1:/ecaJf0sk1l4l6V4awd65v2C3ILy7MSj+s/x1ADCIMU=
go.etcd.io/bbolt v1.3.6/go.mod h1:qXsaaIqmgQH0T+OPdb99Bf+PKfBBQVAdyD6TY9G8XM4=
go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=
@ -167,6 +171,7 @@ golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvx
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@ -191,6 +196,7 @@ golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@ -228,6 +234,7 @@ golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtn
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=

View File

@ -8,6 +8,10 @@ import (
"strings"
"testing"
"github.com/golang/mock/gomock"
"github.com/cosmos/cosmos-sdk/orm/testing/ormmocks"
"google.golang.org/protobuf/reflect/protoreflect"
"gotest.tools/v3/assert"
"gotest.tools/v3/golden"
@ -34,6 +38,19 @@ type keeper struct {
store testpb.BankStore
}
func NewKeeper(db ormdb.ModuleDB) (Keeper, error) {
store, err := testpb.NewBankStore(db)
return keeper{store}, err
}
type Keeper interface {
Send(ctx context.Context, from, to, denom string, amount uint64) error
Mint(ctx context.Context, acct, denom string, amount uint64) error
Burn(ctx context.Context, acct, denom string, amount uint64) error
Balance(ctx context.Context, acct, denom string) (uint64, error)
Supply(ctx context.Context, denom string) (uint64, error)
}
func (k keeper) Send(ctx context.Context, from, to, denom string, amount uint64) error {
err := k.safeSubBalance(ctx, from, denom, amount)
if err != nil {
@ -151,11 +168,6 @@ func (k keeper) safeSubBalance(ctx context.Context, acct, denom string, amount u
}
}
func newKeeper(db ormdb.ModuleDB) (keeper, error) {
store, err := testpb.NewBankStore(db)
return keeper{store}, err
}
func TestModuleDB(t *testing.T) {
// create db & debug context
db, err := ormdb.NewModuleDB(TestBankSchema, ormdb.ModuleDBOptions{})
@ -171,7 +183,7 @@ func TestModuleDB(t *testing.T) {
))
// create keeper
k, err := newKeeper(db)
k, err := NewKeeper(db)
assert.NilError(t, err)
// mint coins
@ -250,3 +262,39 @@ func TestModuleDB(t *testing.T) {
assert.NilError(t, db.ImportJSON(ctx2, source))
testkv.AssertBackendsEqual(t, backend, backend2)
}
func TestHooks(t *testing.T) {
ctrl := gomock.NewController(t)
db, err := ormdb.NewModuleDB(TestBankSchema, ormdb.ModuleDBOptions{})
assert.NilError(t, err)
hooks := ormmocks.NewMockHooks(ctrl)
ctx := ormtable.WrapContextDefault(ormtest.NewMemoryBackend().WithHooks(hooks))
k, err := NewKeeper(db)
assert.NilError(t, err)
denom := "foo"
acct1 := "bob"
acct2 := "sally"
hooks.EXPECT().OnInsert(ormmocks.Eq(&testpb.Balance{Address: acct1, Denom: denom, Amount: 10}))
hooks.EXPECT().OnInsert(ormmocks.Eq(&testpb.Supply{Denom: denom, Amount: 10}))
assert.NilError(t, k.Mint(ctx, acct1, denom, 10))
hooks.EXPECT().OnUpdate(
ormmocks.Eq(&testpb.Balance{Address: acct1, Denom: denom, Amount: 10}),
ormmocks.Eq(&testpb.Balance{Address: acct1, Denom: denom, Amount: 5}),
)
hooks.EXPECT().OnInsert(
ormmocks.Eq(&testpb.Balance{Address: acct2, Denom: denom, Amount: 5}),
)
assert.NilError(t, k.Send(ctx, acct1, acct2, denom, 5))
hooks.EXPECT().OnUpdate(
ormmocks.Eq(&testpb.Supply{Denom: denom, Amount: 10}),
ormmocks.Eq(&testpb.Supply{Denom: denom, Amount: 5}),
)
hooks.EXPECT().OnDelete(
ormmocks.Eq(&testpb.Balance{Address: acct1, Denom: denom, Amount: 5}),
)
assert.NilError(t, k.Burn(ctx, acct1, denom, 5))
}

View File

@ -32,6 +32,9 @@ type Backend interface {
// Hooks returns a Hooks instance or nil.
Hooks() Hooks
// WithHooks returns a copy of this backend with the provided hooks.
WithHooks(Hooks) Backend
}
// ReadBackendOptions defines options for creating a ReadBackend.
@ -82,6 +85,11 @@ type backend struct {
hooks Hooks
}
func (c backend) WithHooks(hooks Hooks) Backend {
c.hooks = hooks
return c
}
func (backend) private() {}
func (c backend) CommitmentStoreReader() kv.ReadonlyStore {

View File

@ -0,0 +1,13 @@
// Package ormmocks contains generated mocks for orm types that can be used
// in testing. Right now, this package only contains a mock for ormtable.Hooks
// as this useful way for unit testing using an in-memory database. Rather
// than attempting to mock a whole table or database instance, instead
// a mock Hook instance can be passed in to verify that the expected
// insert/update/delete operations are happening in the database.
//
// The Eq function gomock.Matcher that compares protobuf messages can
// be used in gomock EXPECT functions.
//
// See TestHooks in ormdb/module_test.go for examples of how to use
// mock Hooks in a real-world scenario.
package ormmocks

View File

@ -0,0 +1,77 @@
// Code generated by MockGen. DO NOT EDIT.
// Source: orm/model/ormtable/hooks.go
// Package ormmocks is a generated GoMock package.
package ormmocks
import (
reflect "reflect"
gomock "github.com/golang/mock/gomock"
proto "google.golang.org/protobuf/proto"
)
// MockHooks is a mock of Hooks interface.
type MockHooks struct {
ctrl *gomock.Controller
recorder *MockHooksMockRecorder
}
// MockHooksMockRecorder is the mock recorder for MockHooks.
type MockHooksMockRecorder struct {
mock *MockHooks
}
// NewMockHooks creates a new mock instance.
func NewMockHooks(ctrl *gomock.Controller) *MockHooks {
mock := &MockHooks{ctrl: ctrl}
mock.recorder = &MockHooksMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use.
func (m *MockHooks) EXPECT() *MockHooksMockRecorder {
return m.recorder
}
// OnDelete mocks base method.
func (m *MockHooks) OnDelete(arg0 proto.Message) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "OnDelete", arg0)
ret0, _ := ret[0].(error)
return ret0
}
// OnDelete indicates an expected call of OnDelete.
func (mr *MockHooksMockRecorder) OnDelete(arg0 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "OnDelete", reflect.TypeOf((*MockHooks)(nil).OnDelete), arg0)
}
// OnInsert mocks base method.
func (m *MockHooks) OnInsert(arg0 proto.Message) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "OnInsert", arg0)
ret0, _ := ret[0].(error)
return ret0
}
// OnInsert indicates an expected call of OnInsert.
func (mr *MockHooksMockRecorder) OnInsert(arg0 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "OnInsert", reflect.TypeOf((*MockHooks)(nil).OnInsert), arg0)
}
// OnUpdate mocks base method.
func (m *MockHooks) OnUpdate(existing, new proto.Message) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "OnUpdate", existing, new)
ret0, _ := ret[0].(error)
return ret0
}
// OnUpdate indicates an expected call of OnUpdate.
func (mr *MockHooksMockRecorder) OnUpdate(existing, new interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "OnUpdate", reflect.TypeOf((*MockHooks)(nil).OnUpdate), existing, new)
}

View File

@ -0,0 +1,29 @@
package ormmocks
import (
"github.com/golang/mock/gomock"
"github.com/google/go-cmp/cmp"
"google.golang.org/protobuf/proto"
"google.golang.org/protobuf/testing/protocmp"
)
// Code adapted from MIT-licensed https://github.com/budougumi0617/cmpmock/blob/master/diffmatcher.go
// Eq returns a gomock.Matcher which uses go-cmp to compare protobuf messages.
func Eq(message proto.Message) gomock.Matcher {
return &protoEq{message: message}
}
type protoEq struct {
message interface{}
diff string
}
func (p protoEq) Matches(x interface{}) bool {
p.diff = cmp.Diff(x, p.message, protocmp.Transform())
return len(p.diff) == 0
}
func (p protoEq) String() string {
return p.diff
}