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:
parent
4addb7368c
commit
77ac8fa378
1
Makefile
1
Makefile
@ -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):
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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=
|
||||
|
||||
@ -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))
|
||||
}
|
||||
|
||||
@ -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 {
|
||||
|
||||
13
orm/testing/ormmocks/docs.go
Normal file
13
orm/testing/ormmocks/docs.go
Normal 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
|
||||
77
orm/testing/ormmocks/hooks.go
Normal file
77
orm/testing/ormmocks/hooks.go
Normal 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)
|
||||
}
|
||||
29
orm/testing/ormmocks/match.go
Normal file
29
orm/testing/ormmocks/match.go
Normal 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
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user