chore!: Refactor x/bank CLI Tests (#12706)

This commit is contained in:
Aleksandr Bezobchuk 2022-08-03 12:08:41 -04:00 committed by GitHub
parent 71035879e4
commit ccc8003a80
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 758 additions and 32 deletions

View File

@ -45,6 +45,7 @@ Ref: https://keepachangelog.com/en/1.0.0/
### Improvements
* (x/bank) [#12706](https://github.com/cosmos/cosmos-sdk/pull/12706) Added the `chain-id` flag to the `AddTxFlagsToCmd` API. There is no longer a need to explicitly register this flag on commands whens `AddTxFlagsToCmd` is already called.
* [#12791](https://github.com/cosmos/cosmos-sdk/pull/12791) Bump the math library used in the sdk and replace old usages of sdk.*
* (x/params) [#12615](https://github.com/cosmos/cosmos-sdk/pull/12615) Add `GetParamSetIfExists` function to params `Subspace` to prevent panics on breaking changes.
* [#12717](https://github.com/cosmos/cosmos-sdk/pull/12717) Use injected encoding params in simapp.
@ -70,6 +71,7 @@ Ref: https://keepachangelog.com/en/1.0.0/
### API Breaking Changes
* (x/bank) [#12706](https://github.com/cosmos/cosmos-sdk/pull/12706) Removed the `testutil` package from the `x/bank/client` package.
* (simapp) [#12747](https://github.com/cosmos/cosmos-sdk/pull/12747) Remove `simapp.MakeTestEncodingConfig`. Please use `moduletestutil.MakeTestEncodingConfig` (`types/module/testutil`) in tests instead.
* (x/bank) [#12648](https://github.com/cosmos/cosmos-sdk/pull/12648) `NewSendAuthorization` takes a new argument of an optional list of addresses allowed to receive bank assests via authz MsgSend grant. You can pass `nil` for the same behavior as before, i.e. any recipient is allowed.
* (x/bank) [\#12593](https://github.com/cosmos/cosmos-sdk/pull/12593) Add `SpendableCoin` method to `BaseViewKeeper`

View File

@ -22,3 +22,26 @@ type AccountRetriever interface {
EnsureExists(clientCtx Context, addr sdk.AccAddress) error
GetAccountNumberSequence(clientCtx Context, addr sdk.AccAddress) (accNum uint64, accSeq uint64, err error)
}
var _ AccountRetriever = (*MockAccountRetriever)(nil)
// MockAccountRetriever defines a no-op basic AccountRetriever that can be used
// in mocked contexts. Tests or context that need more sophisticated testing
// state should implement their own mock AccountRetriever.
type MockAccountRetriever struct{}
func (mar MockAccountRetriever) GetAccount(_ Context, _ sdk.AccAddress) (Account, error) {
return nil, nil
}
func (mar MockAccountRetriever) GetAccountWithHeight(_ Context, _ sdk.AccAddress) (Account, int64, error) {
return nil, 0, nil
}
func (mar MockAccountRetriever) EnsureExists(_ Context, _ sdk.AccAddress) error {
return nil
}
func (mar MockAccountRetriever) GetAccountNumberSequence(_ Context, _ sdk.AccAddress) (uint64, uint64, error) {
return 0, 0, nil
}

View File

@ -7,14 +7,10 @@ import (
"io"
"os"
"github.com/spf13/viper"
"sigs.k8s.io/yaml"
"google.golang.org/grpc"
"github.com/gogo/protobuf/proto"
rpcclient "github.com/tendermint/tendermint/rpc/client"
"github.com/spf13/viper"
"google.golang.org/grpc"
"sigs.k8s.io/yaml"
"github.com/cosmos/cosmos-sdk/codec"
codectypes "github.com/cosmos/cosmos-sdk/codec/types"
@ -26,7 +22,7 @@ import (
// handling and queries.
type Context struct {
FromAddress sdk.AccAddress
Client rpcclient.Client
Client TendermintRPC
GRPCClient *grpc.ClientConn
ChainID string
Codec codec.Codec
@ -128,7 +124,7 @@ func (ctx Context) WithHeight(height int64) Context {
// WithClient returns a copy of the context with an updated RPC client
// instance.
func (ctx Context) WithClient(client rpcclient.Client) Context {
func (ctx Context) WithClient(client TendermintRPC) Context {
ctx.Client = client
return ctx
}

View File

@ -79,6 +79,7 @@ const (
FlagReverse = "reverse"
FlagTip = "tip"
FlagAux = "aux"
FlagOutput = tmcli.OutputFlag
// Tendermint logging flags
FlagLogLevel = "log_level"
@ -93,7 +94,7 @@ var LineBreak = &cobra.Command{Run: func(*cobra.Command, []string) {}}
func AddQueryFlagsToCmd(cmd *cobra.Command) {
cmd.Flags().String(FlagNode, "tcp://localhost:26657", "<host>:<port> to Tendermint RPC interface for this chain")
cmd.Flags().Int64(FlagHeight, 0, "Use a specific height to query state at (this can error if the node is pruning state)")
cmd.Flags().StringP(tmcli.OutputFlag, "o", "text", "Output format (text|json)")
cmd.Flags().StringP(FlagOutput, "o", "text", "Output format (text|json)")
// some base commands does not require chainID e.g `simd testnet` while subcommands do
// hence the flag should not be required for those commands
@ -102,7 +103,7 @@ func AddQueryFlagsToCmd(cmd *cobra.Command) {
// AddTxFlagsToCmd adds common flags to a module tx command.
func AddTxFlagsToCmd(cmd *cobra.Command) {
cmd.Flags().StringP(tmcli.OutputFlag, "o", "json", "Output format (text|json)")
cmd.Flags().StringP(FlagOutput, "o", "json", "Output format (text|json)")
cmd.Flags().String(FlagKeyringDir, "", "The client Keyring directory; if omitted, the default 'home' directory will be used")
cmd.Flags().String(FlagFrom, "", "Name or address of private key with which to sign")
cmd.Flags().Uint64P(FlagAccountNumber, "a", 0, "The account number of the signing account (offline mode only)")
@ -125,6 +126,7 @@ func AddTxFlagsToCmd(cmd *cobra.Command) {
cmd.Flags().String(FlagFeeGranter, "", "Fee granter grants fees for the transaction")
cmd.Flags().String(FlagTip, "", "Tip is the amount that is going to be transferred to the fee payer on the target chain. This flag is only valid when used with --aux, and is ignored if the target chain didn't enable the TipDecorator")
cmd.Flags().Bool(FlagAux, false, "Generate aux signer data instead of sending a tx")
cmd.Flags().String(FlagChainID, "", "The network chain ID")
// --gas can accept integers and "auto"
cmd.Flags().String(FlagGas, "", fmt.Sprintf("gas limit to set per-transaction; set to %q to calculate sufficient gas automatically (default %d)", GasFlagAuto, DefaultGasLimit))

View File

@ -7,10 +7,11 @@ import (
"reflect"
"strconv"
"github.com/cosmos/cosmos-sdk/codec"
proto "github.com/gogo/protobuf/proto"
"google.golang.org/grpc/encoding"
"github.com/cosmos/cosmos-sdk/codec"
gogogrpc "github.com/gogo/protobuf/grpc"
abci "github.com/tendermint/tendermint/abci/types"
"google.golang.org/grpc"

View File

@ -6,12 +6,11 @@ import (
"strings"
"github.com/pkg/errors"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
abci "github.com/tendermint/tendermint/abci/types"
tmbytes "github.com/tendermint/tendermint/libs/bytes"
rpcclient "github.com/tendermint/tendermint/rpc/client"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"github.com/cosmos/cosmos-sdk/store/rootmulti"
sdk "github.com/cosmos/cosmos-sdk/types"
@ -20,7 +19,7 @@ import (
// GetNode returns an RPC client. If the context's client is not defined, an
// error is returned.
func (ctx Context) GetNode() (rpcclient.Client, error) {
func (ctx Context) GetNode() (TendermintRPC, error) {
if ctx.Client == nil {
return nil, errors.New("no RPC client is defined in offline mode")
}

28
client/tendermint.go Normal file
View File

@ -0,0 +1,28 @@
package client
import (
"context"
"github.com/tendermint/tendermint/libs/bytes"
rpcclient "github.com/tendermint/tendermint/rpc/client"
"github.com/tendermint/tendermint/rpc/coretypes"
)
// TendermintRPC defines the interface of a Tendermint RPC client needed for
// queries and transaction handling.
type TendermintRPC interface {
rpcclient.ABCIClient
Validators(ctx context.Context, height *int64, page, perPage *int) (*coretypes.ResultValidators, error)
Status(context.Context) (*coretypes.ResultStatus, error)
Block(ctx context.Context, height *int64) (*coretypes.ResultBlock, error)
BlockchainInfo(ctx context.Context, minHeight, maxHeight int64) (*coretypes.ResultBlockchainInfo, error)
Tx(ctx context.Context, hash bytes.HexBytes, prove bool) (*coretypes.ResultTx, error)
TxSearch(
ctx context.Context,
query string,
prove bool,
page, perPage *int,
orderBy string,
) (*coretypes.ResultTxSearch, error)
}

View File

@ -392,7 +392,6 @@ func (f Factory) getSimPK() (cryptotypes.PubKey, error) {
// the updated fields will be returned.
func (f Factory) Prepare(clientCtx client.Context) (Factory, error) {
fc := f
from := clientCtx.GetFromAddress()
if err := fc.accountRetriever.EnsureExists(clientCtx, from); err != nil {

View File

@ -20,14 +20,11 @@ import (
func Execute(rootCmd *cobra.Command, envPrefix string, defaultHome string) error {
// Create and set a client.Context on the command's Context. During the pre-run
// of the root command, a default initialized client.Context is provided to
// seed child command execution with values such as AccountRetriver, Keyring,
// seed child command execution with values such as AccountRetriever, Keyring,
// and a Tendermint RPC. This requires the use of a pointer reference when
// getting and setting the client.Context. Ideally, we utilize
// https://github.com/spf13/cobra/pull/1118.
srvCtx := server.NewDefaultContext()
ctx := context.Background()
ctx = context.WithValue(ctx, client.ClientContextKey, &client.Context{})
ctx = context.WithValue(ctx, server.ServerContextKey, srvCtx)
ctx := CreateExecuteContext(context.Background())
rootCmd.PersistentFlags().String(flags.FlagLogLevel, zerolog.InfoLevel.String(), "The logging level (trace|debug|info|warn|error|fatal|panic)")
rootCmd.PersistentFlags().String(flags.FlagLogFormat, tmlog.LogFormatPlain, "The logging format (json|plain)")
@ -35,3 +32,13 @@ func Execute(rootCmd *cobra.Command, envPrefix string, defaultHome string) error
executor := tmcli.PrepareBaseCmd(rootCmd, envPrefix, defaultHome)
return executor.ExecuteContext(ctx)
}
// CreateExecuteContext returns a base Context with server and client context
// values initialized.
func CreateExecuteContext(ctx context.Context) context.Context {
srvCtx := server.NewDefaultContext()
ctx = context.WithValue(ctx, client.ClientContextKey, &client.Context{})
ctx = context.WithValue(ctx, server.ServerContextKey, srvCtx)
return ctx
}

View File

@ -216,7 +216,6 @@ func queryCommand() *cobra.Command {
)
simapp.ModuleBasics.AddQueryCommands(cmd)
cmd.PersistentFlags().String(flags.FlagChainID, "", "The network chain ID")
return cmd
}
@ -243,7 +242,6 @@ func txCommand() *cobra.Command {
)
simapp.ModuleBasics.AddTxCommands(cmd)
cmd.PersistentFlags().String(flags.FlagChainID, "", "The network chain ID")
return cmd
}

View File

@ -76,7 +76,7 @@ func GetAuxToFeeCommand() *cobra.Command {
}
flags.AddTxFlagsToCmd(cmd)
cmd.Flags().String(flags.FlagChainID, "", "network chain ID")
return cmd
}

View File

@ -65,7 +65,6 @@ The SIGN_MODE_DIRECT sign mode is not supported.'
cmd.Flags().String(flags.FlagOutputDocument, "", "The document is written to the given file instead of STDOUT")
cmd.Flags().Bool(flagAmino, false, "Generate Amino-encoded JSON suitable for submitting to the txs REST endpoint")
flags.AddTxFlagsToCmd(cmd)
cmd.Flags().String(flags.FlagChainID, "", "network chain ID")
return cmd
}

View File

@ -54,7 +54,6 @@ account key. It implies --signature-only.
cmd.Flags().String(flagMultisig, "", "Address or key name of the multisig account on behalf of which the transaction shall be signed")
cmd.Flags().String(flags.FlagOutputDocument, "", "The document will be written to the given file instead of STDOUT")
cmd.Flags().Bool(flagSigOnly, true, "Print only the generated signature, then exit")
cmd.Flags().String(flags.FlagChainID, "", "network chain ID")
flags.AddTxFlagsToCmd(cmd)
cmd.MarkFlagRequired(flags.FlagFrom)
@ -192,7 +191,6 @@ be generated via the 'multisign' command.
cmd.Flags().Bool(flagOverwrite, false, "Overwrite existing signatures with a new one. If disabled, new signature will be appended")
cmd.Flags().Bool(flagSigOnly, false, "Print only the signatures")
cmd.Flags().String(flags.FlagOutputDocument, "", "The document will be written to the given file instead of STDOUT")
cmd.Flags().String(flags.FlagChainID, "", "The network chain ID")
cmd.Flags().Bool(flagAmino, false, "Generate Amino encoded JSON suitable for submiting to the txs REST endpoint")
flags.AddTxFlagsToCmd(cmd)

View File

@ -30,7 +30,6 @@ transaction will be not be performed as that will require RPC communication with
Args: cobra.ExactArgs(1),
}
cmd.Flags().String(flags.FlagChainID, "", "The network chain ID")
flags.AddTxFlagsToCmd(cmd)
return cmd

View File

@ -59,6 +59,7 @@ Example:
if err != nil {
return err
}
denom, err := cmd.Flags().GetString(FlagDenom)
if err != nil {
return err
@ -75,17 +76,22 @@ Example:
if err != nil {
return err
}
ctx := cmd.Context()
if denom == "" {
params := types.NewQueryAllBalancesRequest(addr, pageReq)
res, err := queryClient.AllBalances(ctx, params)
if err != nil {
return err
}
return clientCtx.PrintProto(res)
}
params := types.NewQueryBalanceRequest(addr, denom)
res, err := queryClient.Balance(ctx, params)
if err != nil {
return err
@ -125,6 +131,7 @@ To query for the client metadata of a specific coin denomination use:
if err != nil {
return err
}
denom, err := cmd.Flags().GetString(FlagDenom)
if err != nil {
return err
@ -178,6 +185,7 @@ To query for the total supply of a specific coin denomination use:
if err != nil {
return err
}
denom, err := cmd.Flags().GetString(FlagDenom)
if err != nil {
return err
@ -190,6 +198,7 @@ To query for the total supply of a specific coin denomination use:
if err != nil {
return err
}
if denom == "" {
res, err := queryClient.TotalSupply(ctx, &types.QueryTotalSupplyRequest{Pagination: pageReq})
if err != nil {

View File

@ -0,0 +1,362 @@
package cli_test
import (
"bytes"
"context"
"fmt"
"io"
"github.com/gogo/protobuf/proto"
abci "github.com/tendermint/tendermint/abci/types"
"github.com/cosmos/cosmos-sdk/client"
"github.com/cosmos/cosmos-sdk/client/flags"
svrcmd "github.com/cosmos/cosmos-sdk/server/cmd"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/x/bank/client/cli"
"github.com/cosmos/cosmos-sdk/x/bank/types"
)
func (s *CLITestSuite) TestGetBalancesCmd() {
accounts := s.createKeyringAccounts(1)
cmd := cli.GetBalancesCmd()
cmd.SetOutput(io.Discard)
testCases := []struct {
name string
ctxGen func() client.Context
args []string
expectResult proto.Message
expectErr bool
}{
{
"valid query",
func() client.Context {
bz, _ := s.encCfg.Codec.Marshal(&types.QueryAllBalancesResponse{})
c := newMockTendermintRPC(abci.ResponseQuery{
Value: bz,
})
return s.baseCtx.WithClient(c)
},
[]string{
accounts[0].address.String(),
fmt.Sprintf("--%s=json", flags.FlagOutput),
},
&types.QueryAllBalancesResponse{},
false,
},
{
"valid query with denom",
func() client.Context {
bz, _ := s.encCfg.Codec.Marshal(&types.QueryBalanceResponse{
Balance: &sdk.Coin{},
})
c := newMockTendermintRPC(abci.ResponseQuery{
Value: bz,
})
return s.baseCtx.WithClient(c)
},
[]string{
accounts[0].address.String(),
fmt.Sprintf("--%s=photon", cli.FlagDenom),
fmt.Sprintf("--%s=json", flags.FlagOutput),
},
&sdk.Coin{},
false,
},
{
"invalid address",
func() client.Context {
return s.baseCtx
},
[]string{
"foo",
},
nil,
true,
},
{
"invalid denom",
func() client.Context {
c := newMockTendermintRPC(abci.ResponseQuery{
Code: 1,
})
return s.baseCtx.WithClient(c)
},
[]string{
accounts[0].address.String(),
fmt.Sprintf("--%s=foo", cli.FlagDenom),
},
nil,
true,
},
}
for _, tc := range testCases {
tc := tc
s.Run(tc.name, func() {
var outBuf bytes.Buffer
clientCtx := tc.ctxGen().WithOutput(&outBuf)
ctx := svrcmd.CreateExecuteContext(context.Background())
cmd.SetContext(ctx)
cmd.SetArgs(tc.args)
s.Require().NoError(client.SetCmdClientContextHandler(clientCtx, cmd))
err := cmd.Execute()
if tc.expectErr {
s.Require().Error(err)
} else {
s.Require().NoError(s.encCfg.Codec.UnmarshalJSON(outBuf.Bytes(), tc.expectResult))
s.Require().NoError(err)
}
})
}
}
func (s *CLITestSuite) TestGetCmdDenomsMetadata() {
cmd := cli.GetCmdDenomsMetadata()
cmd.SetOutput(io.Discard)
testCases := []struct {
name string
ctxGen func() client.Context
args []string
expectResult proto.Message
expectErr bool
}{
{
"valid query",
func() client.Context {
bz, _ := s.encCfg.Codec.Marshal(&types.QueryDenomsMetadataResponse{})
c := newMockTendermintRPC(abci.ResponseQuery{
Value: bz,
})
return s.baseCtx.WithClient(c)
},
[]string{
fmt.Sprintf("--%s=json", flags.FlagOutput),
},
&types.QueryDenomsMetadataResponse{},
false,
},
{
"valid query with denom",
func() client.Context {
bz, _ := s.encCfg.Codec.Marshal(&types.QueryDenomMetadataResponse{})
c := newMockTendermintRPC(abci.ResponseQuery{
Value: bz,
})
return s.baseCtx.WithClient(c)
},
[]string{
fmt.Sprintf("--%s=photon", cli.FlagDenom),
fmt.Sprintf("--%s=json", flags.FlagOutput),
},
&types.QueryDenomMetadataResponse{},
false,
},
{
"invalid query with denom",
func() client.Context {
c := newMockTendermintRPC(abci.ResponseQuery{
Code: 1,
})
return s.baseCtx.WithClient(c)
},
[]string{
fmt.Sprintf("--%s=foo", cli.FlagDenom),
},
nil,
true,
},
}
for _, tc := range testCases {
tc := tc
s.Run(tc.name, func() {
var outBuf bytes.Buffer
clientCtx := tc.ctxGen().WithOutput(&outBuf)
ctx := svrcmd.CreateExecuteContext(context.Background())
cmd.SetContext(ctx)
cmd.SetArgs(tc.args)
s.Require().NoError(client.SetCmdClientContextHandler(clientCtx, cmd))
err := cmd.Execute()
if tc.expectErr {
s.Require().Error(err)
} else {
s.Require().NoError(s.encCfg.Codec.UnmarshalJSON(outBuf.Bytes(), tc.expectResult))
s.Require().NoError(err)
}
})
}
}
func (s *CLITestSuite) TestGetCmdQueryTotalSupply() {
cmd := cli.GetCmdQueryTotalSupply()
cmd.SetOutput(io.Discard)
testCases := []struct {
name string
ctxGen func() client.Context
args []string
expectResult proto.Message
expectErr bool
}{
{
"valid query",
func() client.Context {
bz, _ := s.encCfg.Codec.Marshal(&types.QueryTotalSupplyResponse{})
c := newMockTendermintRPC(abci.ResponseQuery{
Value: bz,
})
return s.baseCtx.WithClient(c)
},
[]string{
fmt.Sprintf("--%s=json", flags.FlagOutput),
},
&types.QueryTotalSupplyResponse{},
false,
},
{
"valid query with denom",
func() client.Context {
bz, _ := s.encCfg.Codec.Marshal(&types.QuerySupplyOfResponse{
Amount: sdk.Coin{},
})
c := newMockTendermintRPC(abci.ResponseQuery{
Value: bz,
})
return s.baseCtx.WithClient(c)
},
[]string{
fmt.Sprintf("--%s=photon", cli.FlagDenom),
fmt.Sprintf("--%s=json", flags.FlagOutput),
},
&sdk.Coin{},
false,
},
{
"invalid query with denom",
func() client.Context {
c := newMockTendermintRPC(abci.ResponseQuery{
Code: 1,
})
return s.baseCtx.WithClient(c)
},
[]string{
fmt.Sprintf("--%s=foo", cli.FlagDenom),
fmt.Sprintf("--%s=json", flags.FlagOutput),
},
nil,
true,
},
}
for _, tc := range testCases {
tc := tc
s.Run(tc.name, func() {
var outBuf bytes.Buffer
clientCtx := tc.ctxGen().WithOutput(&outBuf)
ctx := svrcmd.CreateExecuteContext(context.Background())
cmd.SetContext(ctx)
cmd.SetArgs(tc.args)
s.Require().NoError(client.SetCmdClientContextHandler(clientCtx, cmd))
err := cmd.Execute()
if tc.expectErr {
s.Require().Error(err)
} else {
s.Require().NoError(s.encCfg.Codec.UnmarshalJSON(outBuf.Bytes(), tc.expectResult))
s.Require().NoError(err)
}
})
}
}
func (s *CLITestSuite) TestGetCmdQuerySendEnabled() {
cmd := cli.GetCmdQuerySendEnabled()
cmd.SetOutput(io.Discard)
testCases := []struct {
name string
ctxGen func() client.Context
args []string
expectResult proto.Message
expectErr bool
}{
{
"valid query",
func() client.Context {
bz, _ := s.encCfg.Codec.Marshal(&types.QuerySendEnabledResponse{
SendEnabled: []*types.SendEnabled{},
})
c := newMockTendermintRPC(abci.ResponseQuery{
Value: bz,
})
return s.baseCtx.WithClient(c)
},
[]string{
fmt.Sprintf("--%s=json", flags.FlagOutput),
},
&types.QuerySendEnabledResponse{},
false,
},
{
"valid query with denoms",
func() client.Context {
bz, _ := s.encCfg.Codec.Marshal(&types.QuerySendEnabledResponse{
SendEnabled: []*types.SendEnabled{},
})
c := newMockTendermintRPC(abci.ResponseQuery{
Value: bz,
})
return s.baseCtx.WithClient(c)
},
[]string{
"photon",
"stake",
fmt.Sprintf("--%s=json", flags.FlagOutput),
},
&types.QuerySendEnabledResponse{},
false,
},
}
for _, tc := range testCases {
tc := tc
s.Run(tc.name, func() {
var outBuf bytes.Buffer
clientCtx := tc.ctxGen().WithOutput(&outBuf)
ctx := svrcmd.CreateExecuteContext(context.Background())
cmd.SetContext(ctx)
cmd.SetArgs(tc.args)
s.Require().NoError(client.SetCmdClientContextHandler(clientCtx, cmd))
err := cmd.Execute()
if tc.expectErr {
s.Require().Error(err)
} else {
s.Require().NoError(s.encCfg.Codec.UnmarshalJSON(outBuf.Bytes(), tc.expectResult))
s.Require().NoError(err)
}
})
}
}

View File

@ -0,0 +1,96 @@
package cli_test
import (
"context"
"fmt"
"io"
"testing"
"github.com/stretchr/testify/suite"
abci "github.com/tendermint/tendermint/abci/types"
"github.com/tendermint/tendermint/libs/bytes"
rpcclient "github.com/tendermint/tendermint/rpc/client"
rpcclientmock "github.com/tendermint/tendermint/rpc/client/mock"
"github.com/tendermint/tendermint/rpc/coretypes"
tmtypes "github.com/tendermint/tendermint/types"
"github.com/cosmos/cosmos-sdk/client"
"github.com/cosmos/cosmos-sdk/crypto/hd"
"github.com/cosmos/cosmos-sdk/crypto/keyring"
sdk "github.com/cosmos/cosmos-sdk/types"
testutilmod "github.com/cosmos/cosmos-sdk/types/module/testutil"
"github.com/cosmos/cosmos-sdk/x/bank"
)
var _ client.TendermintRPC = (*mockTendermintRPC)(nil)
type mockTendermintRPC struct {
rpcclientmock.Client
responseQuery abci.ResponseQuery
}
func newMockTendermintRPC(respQuery abci.ResponseQuery) mockTendermintRPC {
return mockTendermintRPC{responseQuery: respQuery}
}
func (_ mockTendermintRPC) BroadcastTxSync(context.Context, tmtypes.Tx) (*coretypes.ResultBroadcastTx, error) {
return &coretypes.ResultBroadcastTx{Code: 0}, nil
}
func (m mockTendermintRPC) ABCIQueryWithOptions(
_ context.Context,
_ string, _ bytes.HexBytes,
_ rpcclient.ABCIQueryOptions,
) (*coretypes.ResultABCIQuery, error) {
return &coretypes.ResultABCIQuery{Response: m.responseQuery}, nil
}
type account struct {
name string
address sdk.AccAddress
}
type CLITestSuite struct {
suite.Suite
kr keyring.Keyring
encCfg testutilmod.TestEncodingConfig
baseCtx client.Context
}
func TestMigrateTestSuite(t *testing.T) {
suite.Run(t, new(CLITestSuite))
}
func (s *CLITestSuite) SetupSuite() {
s.encCfg = testutilmod.MakeTestEncodingConfig(bank.AppModuleBasic{})
s.kr = keyring.NewInMemory(s.encCfg.Codec)
s.baseCtx = client.Context{}.
WithKeyring(s.kr).
WithTxConfig(s.encCfg.TxConfig).
WithCodec(s.encCfg.Codec).
WithClient(mockTendermintRPC{Client: rpcclientmock.New()}).
WithAccountRetriever(client.MockAccountRetriever{}).
WithOutput(io.Discard)
}
func (s *CLITestSuite) createKeyringAccounts(num int) []account {
accounts := make([]account, num)
for i := range accounts {
record, _, err := s.kr.NewMnemonic(
fmt.Sprintf("key-%d", i),
keyring.English,
sdk.FullFundraiserPath,
keyring.DefaultBIP39Passphrase,
hd.Secp256k1)
s.Require().NoError(err)
addr, err := record.GetAddress()
s.Require().NoError(err)
accounts[i] = account{name: record.Name, address: addr}
}
return accounts
}

View File

@ -138,7 +138,6 @@ When using '--dry-run' a key name cannot be used, only a bech32 address.
}
cmd.Flags().Bool(FlagSplit, false, "Send the equally split token amount to each address")
flags.AddTxFlagsToCmd(cmd)
return cmd

View File

@ -0,0 +1,211 @@
package cli_test
import (
"context"
"fmt"
"io"
"github.com/cosmos/cosmos-sdk/client"
"github.com/cosmos/cosmos-sdk/client/flags"
svrcmd "github.com/cosmos/cosmos-sdk/server/cmd"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/x/bank/client/cli"
)
func (s *CLITestSuite) TestSendTxCmd() {
accounts := s.createKeyringAccounts(1)
cmd := cli.NewSendTxCmd()
cmd.SetOutput(io.Discard)
extraArgs := []string{
fmt.Sprintf("--%s=%s", flags.FlagBroadcastMode, flags.BroadcastSync),
fmt.Sprintf("--%s=true", flags.FlagSkipConfirmation),
fmt.Sprintf("--%s=%s", flags.FlagBroadcastMode, flags.BroadcastSync),
fmt.Sprintf("--%s=%s", flags.FlagFees, sdk.NewCoins(sdk.NewCoin("photon", sdk.NewInt(10))).String()),
fmt.Sprintf("--%s=test-chain", flags.FlagChainID),
}
testCases := []struct {
name string
ctxGen func() client.Context
from, to sdk.AccAddress
amount sdk.Coins
extraArgs []string
expectErr bool
}{
{
"valid transaction",
func() client.Context {
return s.baseCtx
},
accounts[0].address,
accounts[0].address,
sdk.NewCoins(
sdk.NewCoin("stake", sdk.NewInt(10)),
sdk.NewCoin("photon", sdk.NewInt(40)),
),
extraArgs,
false,
},
{
"invalid to address",
func() client.Context {
return s.baseCtx
},
accounts[0].address,
sdk.AccAddress{},
sdk.NewCoins(
sdk.NewCoin("stake", sdk.NewInt(10)),
sdk.NewCoin("photon", sdk.NewInt(40)),
),
extraArgs,
true,
},
{
"invalid coins",
func() client.Context {
return s.baseCtx
},
accounts[0].address,
accounts[0].address,
nil,
extraArgs,
true,
},
}
for _, tc := range testCases {
tc := tc
s.Run(tc.name, func() {
ctx := svrcmd.CreateExecuteContext(context.Background())
cmd.SetContext(ctx)
cmd.SetArgs(append([]string{tc.from.String(), tc.to.String(), tc.amount.String()}, tc.extraArgs...))
s.Require().NoError(client.SetCmdClientContextHandler(tc.ctxGen(), cmd))
err := cmd.Execute()
if tc.expectErr {
s.Require().Error(err)
} else {
s.Require().NoError(err)
}
})
}
}
func (s *CLITestSuite) TestMultiSendTxCmd() {
accounts := s.createKeyringAccounts(3)
cmd := cli.NewMultiSendTxCmd()
cmd.SetOutput(io.Discard)
extraArgs := []string{
fmt.Sprintf("--%s=%s", flags.FlagBroadcastMode, flags.BroadcastSync),
fmt.Sprintf("--%s=true", flags.FlagSkipConfirmation),
fmt.Sprintf("--%s=%s", flags.FlagBroadcastMode, flags.BroadcastSync),
fmt.Sprintf("--%s=%s", flags.FlagFees, sdk.NewCoins(sdk.NewCoin("photon", sdk.NewInt(10))).String()),
fmt.Sprintf("--%s=test-chain", flags.FlagChainID),
}
testCases := []struct {
name string
ctxGen func() client.Context
from string
to []string
amount sdk.Coins
extraArgs []string
expectErr bool
}{
{
"valid transaction",
func() client.Context {
return s.baseCtx
},
accounts[0].address.String(),
[]string{
accounts[1].address.String(),
accounts[2].address.String(),
},
sdk.NewCoins(
sdk.NewCoin("stake", sdk.NewInt(10)),
sdk.NewCoin("photon", sdk.NewInt(40)),
),
extraArgs,
false,
},
{
"invalid from address",
func() client.Context {
return s.baseCtx
},
"foo",
[]string{
accounts[1].address.String(),
accounts[2].address.String(),
},
sdk.NewCoins(
sdk.NewCoin("stake", sdk.NewInt(10)),
sdk.NewCoin("photon", sdk.NewInt(40)),
),
extraArgs,
true,
},
{
"invalid recipients",
func() client.Context {
return s.baseCtx
},
accounts[0].address.String(),
[]string{
accounts[1].address.String(),
"bar",
},
sdk.NewCoins(
sdk.NewCoin("stake", sdk.NewInt(10)),
sdk.NewCoin("photon", sdk.NewInt(40)),
),
extraArgs,
true,
},
{
"invalid amount",
func() client.Context {
return s.baseCtx
},
accounts[0].address.String(),
[]string{
accounts[1].address.String(),
accounts[2].address.String(),
},
nil,
extraArgs,
true,
},
}
for _, tc := range testCases {
tc := tc
s.Run(tc.name, func() {
ctx := svrcmd.CreateExecuteContext(context.Background())
var args []string
args = append(args, tc.from)
args = append(args, tc.to...)
args = append(args, tc.amount.String())
args = append(args, tc.extraArgs...)
cmd.SetContext(ctx)
cmd.SetArgs(args)
s.Require().NoError(client.SetCmdClientContextHandler(tc.ctxGen(), cmd))
err := cmd.Execute()
if tc.expectErr {
s.Require().Error(err)
} else {
s.Require().NoError(err)
}
})
}
}

View File

@ -1 +0,0 @@
package testutil

View File

@ -209,7 +209,6 @@ $ %s gentx my-key-name 1000000stake --home=/path/to/home/dir --keyring-backend=o
cmd.Flags().String(flags.FlagHome, defaultNodeHome, "The application home directory")
cmd.Flags().String(flags.FlagOutputDocument, "", "Write the genesis transaction JSON document to the given file instead of the default location")
cmd.Flags().String(flags.FlagChainID, "", "The network chain ID")
cmd.Flags().AddFlagSet(fsCreateValidator)
flags.AddTxFlagsToCmd(cmd)