diff --git a/CHANGELOG.md b/CHANGELOG.md index 7bee349575..74ef913933 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -73,7 +73,7 @@ Ref: https://keepachangelog.com/en/1.0.0/ * [#13048](https://github.com/cosmos/cosmos-sdk/pull/13048) Add handling of AccountNumberStoreKeyPrefix to the x/auth simulation decoder. * [#13101](https://github.com/cosmos/cosmos-sdk/pull/13101) Remove weights from `simapp/params` and `testutil/sims`. They are now in their respective modules. * (simapp) [#13107](https://github.com/cosmos/cosmos-sdk/pull/13107) Call `SetIAVLCacheSize` with the configured value in simapp. - +* [#12398](https://github.com/cosmos/cosmos-sdk/issues/12398) Refactor all `x` modules to unit-test via mocks and decouple `simapp`. ### State Machine Breaking diff --git a/scripts/mockgen.sh b/scripts/mockgen.sh index 7b57d0ce7a..afd6045762 100755 --- a/scripts/mockgen.sh +++ b/scripts/mockgen.sh @@ -26,3 +26,4 @@ $mockgen_cmd -source=x/slashing/types/expected_keepers.go -package testutil -des $mockgen_cmd -source=x/genutil/types/expected_keepers.go -package testutil -destination x/genutil/testutil/expected_keepers_mocks.go $mockgen_cmd -source=x/gov/testutil/expected_keepers.go -package testutil -destination x/gov/testutil/expected_keepers_mocks.go $mockgen_cmd -source=x/staking/types/expected_keepers.go -package testutil -destination x/staking/testutil/expected_keepers_mocks.go +$mockgen_cmd -source=x/auth/vesting/types/expected_keepers.go -package testutil -destination x/auth/vesting/testutil/expected_keepers_mocks.go diff --git a/tests/e2e/auth/vesting/client/testutil/cli_test.go b/tests/e2e/auth/vesting/client/testutil/cli_test.go index 7f3e580db1..e98d19c6dd 100644 --- a/tests/e2e/auth/vesting/client/testutil/cli_test.go +++ b/tests/e2e/auth/vesting/client/testutil/cli_test.go @@ -10,11 +10,10 @@ import ( "github.com/cosmos/cosmos-sdk/simapp" "github.com/cosmos/cosmos-sdk/testutil/network" - testutil2 "github.com/cosmos/cosmos-sdk/x/auth/vesting/client/testutil" ) func TestIntegrationTestSuite(t *testing.T) { cfg := network.DefaultConfig(simapp.NewTestNetworkFixture) cfg.NumValidators = 1 - suite.Run(t, testutil2.NewIntegrationTestSuite(cfg)) + suite.Run(t, NewIntegrationTestSuite(cfg)) } diff --git a/x/auth/vesting/client/testutil/suite.go b/tests/e2e/auth/vesting/client/testutil/suite.go similarity index 100% rename from x/auth/vesting/client/testutil/suite.go rename to tests/e2e/auth/vesting/client/testutil/suite.go diff --git a/x/auth/vesting/msg_server_test.go b/x/auth/vesting/msg_server_test.go new file mode 100644 index 0000000000..574b412002 --- /dev/null +++ b/x/auth/vesting/msg_server_test.go @@ -0,0 +1,254 @@ +package vesting_test + +import ( + "testing" + "time" + + "github.com/golang/mock/gomock" + "github.com/stretchr/testify/suite" + tmproto "github.com/tendermint/tendermint/proto/tendermint/types" + tmtime "github.com/tendermint/tendermint/types/time" + + sdk "github.com/cosmos/cosmos-sdk/types" + authkeeper "github.com/cosmos/cosmos-sdk/x/auth/keeper" + "github.com/cosmos/cosmos-sdk/x/auth/vesting" + "github.com/cosmos/cosmos-sdk/testutil" + moduletestutil "github.com/cosmos/cosmos-sdk/types/module/testutil" + vestingtestutil "github.com/cosmos/cosmos-sdk/x/auth/vesting/testutil" + authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" + vestingtypes "github.com/cosmos/cosmos-sdk/x/auth/vesting/types" +) + +var ( + fromAddr = sdk.AccAddress([]byte("from1________________")) + to1Addr = sdk.AccAddress([]byte("to1__________________")) + to2Addr = sdk.AccAddress([]byte("to2__________________")) + to3Addr = sdk.AccAddress([]byte("to3__________________")) + fooCoin = sdk.NewInt64Coin("foo", 100) + periodCoin = sdk.NewInt64Coin("foo", 20) +) + +type VestingTestSuite struct { + suite.Suite + + ctx sdk.Context + accountKeeper authkeeper.AccountKeeper + bankKeeper *vestingtestutil.MockBankKeeper + msgServer vestingtypes.MsgServer +} + +func (s *VestingTestSuite) SetupTest() { + key := sdk.NewKVStoreKey(authtypes.StoreKey) + testCtx := testutil.DefaultContextWithDB(s.T(), key, sdk.NewTransientStoreKey("transient_test")) + s.ctx = testCtx.Ctx.WithBlockHeader(tmproto.Header{Time: tmtime.Now()}) + encCfg := moduletestutil.MakeTestEncodingConfig() + + maccPerms := map[string][]string{} + + ctrl := gomock.NewController(s.T()) + s.bankKeeper = vestingtestutil.NewMockBankKeeper(ctrl) + s.accountKeeper = authkeeper.NewAccountKeeper( + encCfg.Codec, + key, + authtypes.ProtoBaseAccount, + maccPerms, + "cosmos", + authtypes.NewModuleAddress("gov").String(), + ) + + vestingtypes.RegisterInterfaces(encCfg.InterfaceRegistry) + authtypes.RegisterInterfaces(encCfg.InterfaceRegistry) + s.msgServer = vesting.NewMsgServerImpl(s.accountKeeper, s.bankKeeper) +} + +func (s *VestingTestSuite) TestCreateVestingAccount() { + testCases := map[string]struct { + preRun func() + input *vestingtypes.MsgCreateVestingAccount + expErr bool + expErrMsg string + }{ + "create for existing account": { + preRun: func() { + toAcc := s.accountKeeper.NewAccountWithAddress(s.ctx, to1Addr) + s.bankKeeper.EXPECT().IsSendEnabledCoins(gomock.Any(), fooCoin).Return(nil) + s.accountKeeper.SetAccount(s.ctx, toAcc) + s.bankKeeper.EXPECT().BlockedAddr(to1Addr).Return(false) + }, + input: vestingtypes.NewMsgCreateVestingAccount( + fromAddr, + to1Addr, + sdk.Coins{fooCoin}, + time.Now().Unix(), + true, + ), + expErr: true, + expErrMsg: "already exists", + }, + "create a valid delayed vesting account": { + preRun: func() { + s.bankKeeper.EXPECT().IsSendEnabledCoins(gomock.Any(), fooCoin).Return(nil) + s.bankKeeper.EXPECT().BlockedAddr(to2Addr).Return(false) + s.bankKeeper.EXPECT().SendCoins(gomock.Any(), fromAddr, to2Addr, sdk.Coins{fooCoin}).Return(nil) + }, + input: vestingtypes.NewMsgCreateVestingAccount( + fromAddr, + to2Addr, + sdk.Coins{fooCoin}, + time.Now().Unix(), + true, + ), + expErr: false, + expErrMsg: "", + }, + "create a valid continuous vesting account": { + preRun: func() { + s.bankKeeper.EXPECT().IsSendEnabledCoins(gomock.Any(), fooCoin).Return(nil) + s.bankKeeper.EXPECT().BlockedAddr(to3Addr).Return(false) + s.bankKeeper.EXPECT().SendCoins(gomock.Any(), fromAddr, to3Addr, sdk.Coins{fooCoin}).Return(nil) + }, + input: vestingtypes.NewMsgCreateVestingAccount( + fromAddr, + to3Addr, + sdk.Coins{fooCoin}, + time.Now().Unix(), + false, + ), + expErr: false, + expErrMsg: "", + }, + } + + for name, tc := range testCases { + s.Run(name, func() { + tc.preRun() + _, err := s.msgServer.CreateVestingAccount(s.ctx, tc.input) + if tc.expErr { + s.Require().Error(err) + s.Require().Contains(err.Error(), tc.expErrMsg) + } else { + s.Require().NoError(err) + } + }) + } +} + +func (s *VestingTestSuite) TestCreatePermanentLockedAccount() { + testCases := map[string]struct { + preRun func() + input *vestingtypes.MsgCreatePermanentLockedAccount + expErr bool + expErrMsg string + }{ + "create for existing account": { + preRun: func() { + toAcc := s.accountKeeper.NewAccountWithAddress(s.ctx, to1Addr) + s.bankKeeper.EXPECT().IsSendEnabledCoins(gomock.Any(), fooCoin).Return(nil) + s.bankKeeper.EXPECT().BlockedAddr(to1Addr).Return(false) + s.accountKeeper.SetAccount(s.ctx, toAcc) + }, + input: vestingtypes.NewMsgCreatePermanentLockedAccount( + fromAddr, + to1Addr, + sdk.Coins{fooCoin}, + ), + expErr: true, + expErrMsg: "already exists", + }, + "create a valid permanent locked account": { + preRun: func() { + s.bankKeeper.EXPECT().IsSendEnabledCoins(gomock.Any(), fooCoin).Return(nil) + s.bankKeeper.EXPECT().BlockedAddr(to2Addr).Return(false) + s.bankKeeper.EXPECT().SendCoins(gomock.Any(), fromAddr, to2Addr, sdk.Coins{fooCoin}).Return(nil) + }, + input: vestingtypes.NewMsgCreatePermanentLockedAccount( + fromAddr, + to2Addr, + sdk.Coins{fooCoin}, + ), + expErr: false, + expErrMsg: "", + }, + } + + for name, tc := range testCases { + s.Run(name, func() { + tc.preRun() + _, err := s.msgServer.CreatePermanentLockedAccount(s.ctx, tc.input) + if tc.expErr { + s.Require().Error(err) + s.Require().Contains(err.Error(), tc.expErrMsg) + } else { + s.Require().NoError(err) + } + }) + } +} + +func (s *VestingTestSuite) TestCreatePeriodicVestingAccount() { + testCases := map[string]struct { + preRun func() + input *vestingtypes.MsgCreatePeriodicVestingAccount + expErr bool + expErrMsg string + }{ + "create for existing account": { + preRun: func() { + toAcc := s.accountKeeper.NewAccountWithAddress(s.ctx, to1Addr) + s.accountKeeper.SetAccount(s.ctx, toAcc) + }, + input: vestingtypes.NewMsgCreatePeriodicVestingAccount( + fromAddr, + to1Addr, + time.Now().Unix(), + []vestingtypes.Period{ + { + Length: 10, + Amount: sdk.NewCoins(periodCoin), + }, + }, + ), + expErr: true, + expErrMsg: "already exists", + }, + "create a valid periodic vesting account": { + preRun: func() { + s.bankKeeper.EXPECT().SendCoins(gomock.Any(), fromAddr, to2Addr, gomock.Any()).Return(nil) + }, + input: vestingtypes.NewMsgCreatePeriodicVestingAccount( + fromAddr, + to2Addr, + time.Now().Unix(), + []vestingtypes.Period{ + { + Length: 10, + Amount: sdk.NewCoins(periodCoin), + }, + { + Length: 20, + Amount: sdk.NewCoins(fooCoin), + }, + }, + ), + expErr: false, + expErrMsg: "", + }, + } + + for name, tc := range testCases { + s.Run(name, func() { + tc.preRun() + _, err := s.msgServer.CreatePeriodicVestingAccount(s.ctx, tc.input) + if tc.expErr { + s.Require().Error(err) + s.Require().Contains(err.Error(), tc.expErrMsg) + } else { + s.Require().NoError(err) + } + }) + } +} + +func TestVestingTestSuite(t *testing.T) { + suite.Run(t, new(VestingTestSuite)) +} diff --git a/x/auth/vesting/testutil/expected_keepers_mocks.go b/x/auth/vesting/testutil/expected_keepers_mocks.go new file mode 100644 index 0000000000..fd1fe6140d --- /dev/null +++ b/x/auth/vesting/testutil/expected_keepers_mocks.go @@ -0,0 +1,82 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: x/auth/vesting/types/expected_keepers.go + +// Package testutil is a generated GoMock package. +package testutil + +import ( + reflect "reflect" + + types "github.com/cosmos/cosmos-sdk/types" + gomock "github.com/golang/mock/gomock" +) + +// MockBankKeeper is a mock of BankKeeper interface. +type MockBankKeeper struct { + ctrl *gomock.Controller + recorder *MockBankKeeperMockRecorder +} + +// MockBankKeeperMockRecorder is the mock recorder for MockBankKeeper. +type MockBankKeeperMockRecorder struct { + mock *MockBankKeeper +} + +// NewMockBankKeeper creates a new mock instance. +func NewMockBankKeeper(ctrl *gomock.Controller) *MockBankKeeper { + mock := &MockBankKeeper{ctrl: ctrl} + mock.recorder = &MockBankKeeperMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockBankKeeper) EXPECT() *MockBankKeeperMockRecorder { + return m.recorder +} + +// BlockedAddr mocks base method. +func (m *MockBankKeeper) BlockedAddr(addr types.AccAddress) bool { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "BlockedAddr", addr) + ret0, _ := ret[0].(bool) + return ret0 +} + +// BlockedAddr indicates an expected call of BlockedAddr. +func (mr *MockBankKeeperMockRecorder) BlockedAddr(addr interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "BlockedAddr", reflect.TypeOf((*MockBankKeeper)(nil).BlockedAddr), addr) +} + +// IsSendEnabledCoins mocks base method. +func (m *MockBankKeeper) IsSendEnabledCoins(ctx types.Context, coins ...types.Coin) error { + m.ctrl.T.Helper() + varargs := []interface{}{ctx} + for _, a := range coins { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "IsSendEnabledCoins", varargs...) + ret0, _ := ret[0].(error) + return ret0 +} + +// IsSendEnabledCoins indicates an expected call of IsSendEnabledCoins. +func (mr *MockBankKeeperMockRecorder) IsSendEnabledCoins(ctx interface{}, coins ...interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]interface{}{ctx}, coins...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IsSendEnabledCoins", reflect.TypeOf((*MockBankKeeper)(nil).IsSendEnabledCoins), varargs...) +} + +// SendCoins mocks base method. +func (m *MockBankKeeper) SendCoins(ctx types.Context, fromAddr, toAddr types.AccAddress, amt types.Coins) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "SendCoins", ctx, fromAddr, toAddr, amt) + ret0, _ := ret[0].(error) + return ret0 +} + +// SendCoins indicates an expected call of SendCoins. +func (mr *MockBankKeeperMockRecorder) SendCoins(ctx, fromAddr, toAddr, amt interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SendCoins", reflect.TypeOf((*MockBankKeeper)(nil).SendCoins), ctx, fromAddr, toAddr, amt) +} diff --git a/x/staking/module_test.go b/x/staking/module_test.go index f5f9014c3b..2ff9a3d472 100644 --- a/x/staking/module_test.go +++ b/x/staking/module_test.go @@ -20,7 +20,6 @@ func TestItCreatesModuleAccountOnInitBlock(t *testing.T) { ctx := app.BaseApp.NewContext(false, tmproto.Header{}) acc := accountKeeper.GetAccount(ctx, authtypes.NewModuleAddress(types.BondedPoolName)) - require.NotNil(t, acc) acc = accountKeeper.GetAccount(ctx, authtypes.NewModuleAddress(types.NotBondedPoolName))