From 07c549bfbe0e0ee3fe7bc5befcfaa779d11b99c5 Mon Sep 17 00:00:00 2001 From: Jeancarlo Barrios Date: Wed, 24 Aug 2022 08:25:11 -0600 Subject: [PATCH] feat: add unit test with mocks to x/genutil (#13007) ## Description Closes: [#12759](https://github.com/cosmos/cosmos-sdk/issues/12759) --- ### Author Checklist *All items are required. Please add a note to the item if the item is not applicable and please add links to any relevant follow up issues.* I have... - [ ] included the correct [type prefix](https://github.com/commitizen/conventional-commit-types/blob/v3.0.0/index.json) in the PR title - [ ] added `!` to the type prefix if API or client breaking change - [ ] targeted the correct branch (see [PR Targeting](https://github.com/cosmos/cosmos-sdk/blob/main/CONTRIBUTING.md#pr-targeting)) - [ ] provided a link to the relevant issue or specification - [ ] followed the guidelines for [building modules](https://github.com/cosmos/cosmos-sdk/blob/main/docs/building-modules) - [ ] included the necessary unit and integration [tests](https://github.com/cosmos/cosmos-sdk/blob/main/CONTRIBUTING.md#testing) - [ ] added a changelog entry to `CHANGELOG.md` - [ ] included comments for [documenting Go code](https://blog.golang.org/godoc) - [ ] updated the relevant documentation or specification - [ ] reviewed "Files changed" and left comments if necessary - [ ] confirmed all CI checks have passed ### Reviewers Checklist *All items are required. Please add a note if the item is not applicable and please add your handle next to the items reviewed if you only reviewed selected items.* I have... - [ ] confirmed the correct [type prefix](https://github.com/commitizen/conventional-commit-types/blob/v3.0.0/index.json) in the PR title - [ ] confirmed `!` in the type prefix if API or client breaking change - [ ] confirmed all author checklist items have been addressed - [ ] reviewed state machine logic - [ ] reviewed API design and naming - [ ] reviewed documentation is accurate - [ ] reviewed tests and test coverage - [ ] manually tested (if applicable) --- scripts/mockgen.sh | 1 + x/genutil/gentx_test.go | 337 +++++++++++++++++++ x/genutil/testutil/expected_keepers_mocks.go | 186 ++++++++++ 3 files changed, 524 insertions(+) create mode 100644 x/genutil/gentx_test.go create mode 100644 x/genutil/testutil/expected_keepers_mocks.go diff --git a/scripts/mockgen.sh b/scripts/mockgen.sh index f0b02b5d4d..ed1aced410 100755 --- a/scripts/mockgen.sh +++ b/scripts/mockgen.sh @@ -22,3 +22,4 @@ $mockgen_cmd -source=x/bank/types/expected_keepers.go -package testutil -destina $mockgen_cmd -source=x/group/testutil/expected_keepers.go -package testutil -destination x/group/testutil/expected_keepers_mocks.go $mockgen_cmd -source=x/evidence/types/expected_keepers.go -package testutil -destination x/evidence/testutil/expected_keepers_mocks.go $mockgen_cmd -source=x/slashing/types/expected_keepers.go -package testutil -destination x/slashing/testutil/expected_keepers_mocks.go +$mockgen_cmd -source=x/genutil/types/expected_keepers.go -package testutil -destination x/genutil/testutil/expected_keepers_mocks.go \ No newline at end of file diff --git a/x/genutil/gentx_test.go b/x/genutil/gentx_test.go new file mode 100644 index 0000000000..89047d034f --- /dev/null +++ b/x/genutil/gentx_test.go @@ -0,0 +1,337 @@ +package genutil_test + +import ( + "cosmossdk.io/math" + "encoding/json" + "fmt" + "github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1" + "github.com/cosmos/cosmos-sdk/testutil" + simtestutil "github.com/cosmos/cosmos-sdk/testutil/sims" + sdk "github.com/cosmos/cosmos-sdk/types" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" + moduletestutil "github.com/cosmos/cosmos-sdk/types/module/testutil" + + banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" + "github.com/cosmos/cosmos-sdk/x/genutil" + genutiltestutil "github.com/cosmos/cosmos-sdk/x/genutil/testutil" + "github.com/cosmos/cosmos-sdk/x/genutil/types" + stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" + "github.com/golang/mock/gomock" + "github.com/stretchr/testify/suite" + abci "github.com/tendermint/tendermint/abci/types" + "math/rand" + "testing" + "time" +) + +var ( + priv1 = secp256k1.GenPrivKey() + priv2 = secp256k1.GenPrivKey() + pk1 = priv1.PubKey() + pk2 = priv2.PubKey() + addr1 = sdk.AccAddress(pk1.Address()) + addr2 = sdk.AccAddress(pk2.Address()) + desc = stakingtypes.NewDescription("testname", "", "", "", "") + comm = stakingtypes.CommissionRates{} +) + +// GenTxTestSuite is a test suite to be used with gentx tests. +type GenTxTestSuite struct { + suite.Suite + + ctx sdk.Context + + stakingKeeper *genutiltestutil.MockStakingKeeper + encodingConfig moduletestutil.TestEncodingConfig + msg1, msg2 *stakingtypes.MsgCreateValidator +} + +func (suite *GenTxTestSuite) SetupTest() { + suite.encodingConfig = moduletestutil.MakeTestEncodingConfig(genutil.AppModuleBasic{}) + key := sdk.NewKVStoreKey("a_Store_Key") + tkey := sdk.NewTransientStoreKey("a_transient_store") + suite.ctx = testutil.DefaultContext(key, tkey) + + ctrl := gomock.NewController(suite.T()) + suite.stakingKeeper = genutiltestutil.NewMockStakingKeeper(ctrl) + + stakingtypes.RegisterInterfaces(suite.encodingConfig.InterfaceRegistry) + banktypes.RegisterInterfaces(suite.encodingConfig.InterfaceRegistry) + + var err error + amount := sdk.NewInt64Coin(sdk.DefaultBondDenom, 50) + one := math.OneInt() + suite.msg1, err = stakingtypes.NewMsgCreateValidator( + sdk.ValAddress(pk1.Address()), pk1, amount, desc, comm, one) + suite.NoError(err) + suite.msg2, err = stakingtypes.NewMsgCreateValidator( + sdk.ValAddress(pk2.Address()), pk1, amount, desc, comm, one) + suite.NoError(err) +} + +func (suite *GenTxTestSuite) setAccountBalance(balances []banktypes.Balance) json.RawMessage { + bankGenesisState := banktypes.GenesisState{ + Params: banktypes.Params{DefaultSendEnabled: true}, + Balances: []banktypes.Balance{ + { + Address: "cosmos1fl48vsnmsdzcv85q5d2q4z5ajdha8yu34mf0eh", + Coins: sdk.Coins{sdk.NewInt64Coin(sdk.DefaultBondDenom, 1000000)}, + }, + { + Address: "cosmos1jv65s3grqf6v6jl3dp4t6c9t9rk99cd88lyufl", + Coins: sdk.Coins{sdk.NewInt64Coin(sdk.DefaultBondDenom, 2059726)}, + }, + { + Address: "cosmos1k5lndq46x9xpejdxq52q3ql3ycrphg4qxlfqn7", + Coins: sdk.Coins{sdk.NewInt64Coin(sdk.DefaultBondDenom, 100000000000000)}, + }, + }, + Supply: sdk.Coins{sdk.NewInt64Coin(sdk.DefaultBondDenom, 0)}, + } + for _, balance := range balances { + bankGenesisState.Balances = append(bankGenesisState.Balances, balance) + + } + for _, balance := range bankGenesisState.Balances { + bankGenesisState.Supply.Add(balance.Coins...) + } + bankGenesis, err := suite.encodingConfig.Amino.MarshalJSON(bankGenesisState) // TODO switch this to use Marshaler + suite.Require().NoError(err) + + return bankGenesis +} + +func (suite *GenTxTestSuite) TestSetGenTxsInAppGenesisState() { + var ( + txBuilder = suite.encodingConfig.TxConfig.NewTxBuilder() + genTxs []sdk.Tx + ) + + testCases := []struct { + msg string + malleate func() + expPass bool + }{ + { + "one genesis transaction", + func() { + err := txBuilder.SetMsgs(suite.msg1) + suite.Require().NoError(err) + tx := txBuilder.GetTx() + genTxs = []sdk.Tx{tx} + }, + true, + }, + { + "two genesis transactions", + func() { + err := txBuilder.SetMsgs(suite.msg1, suite.msg2) + suite.Require().NoError(err) + tx := txBuilder.GetTx() + genTxs = []sdk.Tx{tx} + }, + true, + }, + } + + for _, tc := range testCases { + suite.Run(fmt.Sprintf("Case %s", tc.msg), func() { + suite.SetupTest() + cdc := suite.encodingConfig.Codec + txJSONEncoder := suite.encodingConfig.TxConfig.TxJSONEncoder() + + tc.malleate() + appGenesisState, err := genutil.SetGenTxsInAppGenesisState(cdc, txJSONEncoder, make(map[string]json.RawMessage), genTxs) + + if tc.expPass { + suite.Require().NoError(err) + suite.Require().NotNil(appGenesisState[types.ModuleName]) + + var genesisState types.GenesisState + err := cdc.UnmarshalJSON(appGenesisState[types.ModuleName], &genesisState) + suite.Require().NoError(err) + suite.Require().NotNil(genesisState.GenTxs) + } else { + suite.Require().Error(err) + } + }) + } +} + +func (suite *GenTxTestSuite) TestValidateAccountInGenesis() { + var ( + appGenesisState = make(map[string]json.RawMessage) + coins sdk.Coins + ) + + testCases := []struct { + msg string + malleate func() + expPass bool + }{ + { + "no accounts", + func() { + coins = sdk.Coins{sdk.NewInt64Coin(sdk.DefaultBondDenom, 0)} + }, + false, + }, + { + "account without balance in the genesis state", + func() { + coins = sdk.Coins{sdk.NewInt64Coin(sdk.DefaultBondDenom, 0)} + balances := banktypes.Balance{ + Address: addr2.String(), + Coins: sdk.Coins{sdk.NewInt64Coin(sdk.DefaultBondDenom, 50)}, + } + appGenesisState[banktypes.ModuleName] = suite.setAccountBalance([]banktypes.Balance{balances}) + }, + false, + }, + { + "account without enough funds of default bond denom", + func() { + coins = sdk.Coins{sdk.NewInt64Coin(sdk.DefaultBondDenom, 50)} + balances := banktypes.Balance{ + Address: addr1.String(), + Coins: sdk.Coins{sdk.NewInt64Coin(sdk.DefaultBondDenom, 25)}, + } + appGenesisState[banktypes.ModuleName] = suite.setAccountBalance([]banktypes.Balance{balances}) + }, + false, + }, + { + "account with enough funds of default bond denom", + func() { + coins = sdk.Coins{sdk.NewInt64Coin(sdk.DefaultBondDenom, 10)} + balances := banktypes.Balance{ + Address: addr1.String(), + Coins: sdk.Coins{sdk.NewInt64Coin(sdk.DefaultBondDenom, 25)}, + } + appGenesisState[banktypes.ModuleName] = suite.setAccountBalance([]banktypes.Balance{balances}) + }, + true, + }, + } + for _, tc := range testCases { + suite.Run(fmt.Sprintf("Case %s", tc.msg), func() { + suite.SetupTest() + cdc := suite.encodingConfig.Codec + + stakingGenesis, err := cdc.MarshalJSON(&stakingtypes.GenesisState{Params: stakingtypes.DefaultParams()}) // TODO switch this to use Marshaler + suite.Require().NoError(err) + appGenesisState[stakingtypes.ModuleName] = stakingGenesis + + tc.malleate() + err = genutil.ValidateAccountInGenesis( + appGenesisState, banktypes.GenesisBalancesIterator{}, + addr1, coins, cdc, + ) + + if tc.expPass { + suite.Require().NoError(err) + } else { + suite.Require().Error(err) + } + }) + } +} + +func (suite *GenTxTestSuite) TestDeliverGenTxs() { + var ( + genTxs []json.RawMessage + txBuilder = suite.encodingConfig.TxConfig.NewTxBuilder() + ) + + testCases := []struct { + msg string + malleate func() + deliverTxFn func(abci.RequestDeliverTx) abci.ResponseDeliverTx + expPass bool + }{ + { + "no signature supplied", + func() { + err := txBuilder.SetMsgs(suite.msg1) + suite.Require().NoError(err) + + genTxs = make([]json.RawMessage, 1) + tx, err := suite.encodingConfig.TxConfig.TxJSONEncoder()(txBuilder.GetTx()) + suite.Require().NoError(err) + genTxs[0] = tx + }, + func(_ abci.RequestDeliverTx) abci.ResponseDeliverTx { + + return abci.ResponseDeliverTx{ + Code: sdkerrors.ErrNoSignatures.ABCICode(), + GasWanted: int64(10000000), + GasUsed: int64(41913), + Log: "no signatures supplied", + } + }, + false, + }, + { + "success", + func() { + r := rand.New(rand.NewSource(time.Now().UnixNano())) + msg := banktypes.NewMsgSend(addr1, addr2, sdk.Coins{sdk.NewInt64Coin(sdk.DefaultBondDenom, 1)}) + tx, err := simtestutil.GenSignedMockTx( + r, + suite.encodingConfig.TxConfig, + []sdk.Msg{msg}, + sdk.Coins{sdk.NewInt64Coin(sdk.DefaultBondDenom, 10)}, + simtestutil.DefaultGenTxGas, + suite.ctx.ChainID(), + []uint64{7}, + []uint64{0}, + priv1, + ) + suite.Require().NoError(err) + + genTxs = make([]json.RawMessage, 1) + genTx, err := suite.encodingConfig.TxConfig.TxJSONEncoder()(tx) + suite.Require().NoError(err) + genTxs[0] = genTx + }, + func(tx abci.RequestDeliverTx) abci.ResponseDeliverTx { + return abci.ResponseDeliverTx{ + Code: sdkerrors.ErrUnauthorized.ABCICode(), + GasWanted: int64(10000000), + GasUsed: int64(41353), + Log: "signature verification failed; please verify account number (4) and chain-id (): unauthorized", + Codespace: "sdk", + } + }, + true, + }, + } + + for _, tc := range testCases { + suite.Run(fmt.Sprintf("Case %s", tc.msg), func() { + suite.SetupTest() + + tc.malleate() + + if tc.expPass { + suite.Require().NotPanics(func() { + genutil.DeliverGenTxs( + suite.ctx, genTxs, suite.stakingKeeper, tc.deliverTxFn, + suite.encodingConfig.TxConfig, + ) + }) + } else { + _, err := genutil.DeliverGenTxs( + suite.ctx, genTxs, suite.stakingKeeper, tc.deliverTxFn, + suite.encodingConfig.TxConfig, + ) + + suite.Require().Error(err) + } + }) + } +} + +func TestGenTxTestSuite(t *testing.T) { + suite.Run(t, new(GenTxTestSuite)) +} diff --git a/x/genutil/testutil/expected_keepers_mocks.go b/x/genutil/testutil/expected_keepers_mocks.go new file mode 100644 index 0000000000..f51fa2eb27 --- /dev/null +++ b/x/genutil/testutil/expected_keepers_mocks.go @@ -0,0 +1,186 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: x/genutil/types/expected_keepers.go + +// Package testutil is a generated GoMock package. +package testutil + +import ( + json "encoding/json" + reflect "reflect" + + codec "github.com/cosmos/cosmos-sdk/codec" + types "github.com/cosmos/cosmos-sdk/types" + types0 "github.com/cosmos/cosmos-sdk/x/auth/types" + exported "github.com/cosmos/cosmos-sdk/x/bank/exported" + gomock "github.com/golang/mock/gomock" + types1 "github.com/tendermint/tendermint/abci/types" +) + +// MockStakingKeeper is a mock of StakingKeeper interface. +type MockStakingKeeper struct { + ctrl *gomock.Controller + recorder *MockStakingKeeperMockRecorder +} + +// MockStakingKeeperMockRecorder is the mock recorder for MockStakingKeeper. +type MockStakingKeeperMockRecorder struct { + mock *MockStakingKeeper +} + +// NewMockStakingKeeper creates a new mock instance. +func NewMockStakingKeeper(ctrl *gomock.Controller) *MockStakingKeeper { + mock := &MockStakingKeeper{ctrl: ctrl} + mock.recorder = &MockStakingKeeperMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockStakingKeeper) EXPECT() *MockStakingKeeperMockRecorder { + return m.recorder +} + +// ApplyAndReturnValidatorSetUpdates mocks base method. +func (m *MockStakingKeeper) ApplyAndReturnValidatorSetUpdates(arg0 types.Context) ([]types1.ValidatorUpdate, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ApplyAndReturnValidatorSetUpdates", arg0) + ret0, _ := ret[0].([]types1.ValidatorUpdate) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ApplyAndReturnValidatorSetUpdates indicates an expected call of ApplyAndReturnValidatorSetUpdates. +func (mr *MockStakingKeeperMockRecorder) ApplyAndReturnValidatorSetUpdates(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ApplyAndReturnValidatorSetUpdates", reflect.TypeOf((*MockStakingKeeper)(nil).ApplyAndReturnValidatorSetUpdates), arg0) +} + +// MockAccountKeeper is a mock of AccountKeeper interface. +type MockAccountKeeper struct { + ctrl *gomock.Controller + recorder *MockAccountKeeperMockRecorder +} + +// MockAccountKeeperMockRecorder is the mock recorder for MockAccountKeeper. +type MockAccountKeeperMockRecorder struct { + mock *MockAccountKeeper +} + +// NewMockAccountKeeper creates a new mock instance. +func NewMockAccountKeeper(ctrl *gomock.Controller) *MockAccountKeeper { + mock := &MockAccountKeeper{ctrl: ctrl} + mock.recorder = &MockAccountKeeperMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockAccountKeeper) EXPECT() *MockAccountKeeperMockRecorder { + return m.recorder +} + +// IterateAccounts mocks base method. +func (m *MockAccountKeeper) IterateAccounts(ctx types.Context, process func(types0.AccountI) bool) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "IterateAccounts", ctx, process) +} + +// IterateAccounts indicates an expected call of IterateAccounts. +func (mr *MockAccountKeeperMockRecorder) IterateAccounts(ctx, process interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IterateAccounts", reflect.TypeOf((*MockAccountKeeper)(nil).IterateAccounts), ctx, process) +} + +// NewAccount mocks base method. +func (m *MockAccountKeeper) NewAccount(arg0 types.Context, arg1 types0.AccountI) types0.AccountI { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "NewAccount", arg0, arg1) + ret0, _ := ret[0].(types0.AccountI) + return ret0 +} + +// NewAccount indicates an expected call of NewAccount. +func (mr *MockAccountKeeperMockRecorder) NewAccount(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "NewAccount", reflect.TypeOf((*MockAccountKeeper)(nil).NewAccount), arg0, arg1) +} + +// SetAccount mocks base method. +func (m *MockAccountKeeper) SetAccount(arg0 types.Context, arg1 types0.AccountI) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "SetAccount", arg0, arg1) +} + +// SetAccount indicates an expected call of SetAccount. +func (mr *MockAccountKeeperMockRecorder) SetAccount(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetAccount", reflect.TypeOf((*MockAccountKeeper)(nil).SetAccount), arg0, arg1) +} + +// MockGenesisAccountsIterator is a mock of GenesisAccountsIterator interface. +type MockGenesisAccountsIterator struct { + ctrl *gomock.Controller + recorder *MockGenesisAccountsIteratorMockRecorder +} + +// MockGenesisAccountsIteratorMockRecorder is the mock recorder for MockGenesisAccountsIterator. +type MockGenesisAccountsIteratorMockRecorder struct { + mock *MockGenesisAccountsIterator +} + +// NewMockGenesisAccountsIterator creates a new mock instance. +func NewMockGenesisAccountsIterator(ctrl *gomock.Controller) *MockGenesisAccountsIterator { + mock := &MockGenesisAccountsIterator{ctrl: ctrl} + mock.recorder = &MockGenesisAccountsIteratorMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockGenesisAccountsIterator) EXPECT() *MockGenesisAccountsIteratorMockRecorder { + return m.recorder +} + +// IterateGenesisAccounts mocks base method. +func (m *MockGenesisAccountsIterator) IterateGenesisAccounts(cdc *codec.LegacyAmino, appGenesis map[string]json.RawMessage, cb func(types0.AccountI) bool) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "IterateGenesisAccounts", cdc, appGenesis, cb) +} + +// IterateGenesisAccounts indicates an expected call of IterateGenesisAccounts. +func (mr *MockGenesisAccountsIteratorMockRecorder) IterateGenesisAccounts(cdc, appGenesis, cb interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IterateGenesisAccounts", reflect.TypeOf((*MockGenesisAccountsIterator)(nil).IterateGenesisAccounts), cdc, appGenesis, cb) +} + +// MockGenesisBalancesIterator is a mock of GenesisBalancesIterator interface. +type MockGenesisBalancesIterator struct { + ctrl *gomock.Controller + recorder *MockGenesisBalancesIteratorMockRecorder +} + +// MockGenesisBalancesIteratorMockRecorder is the mock recorder for MockGenesisBalancesIterator. +type MockGenesisBalancesIteratorMockRecorder struct { + mock *MockGenesisBalancesIterator +} + +// NewMockGenesisBalancesIterator creates a new mock instance. +func NewMockGenesisBalancesIterator(ctrl *gomock.Controller) *MockGenesisBalancesIterator { + mock := &MockGenesisBalancesIterator{ctrl: ctrl} + mock.recorder = &MockGenesisBalancesIteratorMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockGenesisBalancesIterator) EXPECT() *MockGenesisBalancesIteratorMockRecorder { + return m.recorder +} + +// IterateGenesisBalances mocks base method. +func (m *MockGenesisBalancesIterator) IterateGenesisBalances(cdc codec.JSONCodec, appGenesis map[string]json.RawMessage, cb func(exported.GenesisBalance) bool) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "IterateGenesisBalances", cdc, appGenesis, cb) +} + +// IterateGenesisBalances indicates an expected call of IterateGenesisBalances. +func (mr *MockGenesisBalancesIteratorMockRecorder) IterateGenesisBalances(cdc, appGenesis, cb interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IterateGenesisBalances", reflect.TypeOf((*MockGenesisBalancesIterator)(nil).IterateGenesisBalances), cdc, appGenesis, cb) +}