Skip to content

Commit

Permalink
test: TxService.Broadcast (#2833)
Browse files Browse the repository at this point in the history
The change introduces a new interface to mock the call to tx.Sign(). I
found more interesting to assert the call to that function rather than
trying to assert its behavior in term of code, which is basically
testing the cosmos-sdk, and we don't want to do that.

By adding the test I found a bug in the error comparison that I
introduced previously with the rpcWrapper, so I fixed it.

Co-authored-by: Alex Johnson <[email protected]>
  • Loading branch information
tbruyelle and Alex Johnson authored Sep 17, 2022
1 parent 069975a commit 529407a
Show file tree
Hide file tree
Showing 7 changed files with 399 additions and 102 deletions.
17 changes: 17 additions & 0 deletions ignite/pkg/cosmosclient/cosmosclient.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,11 @@ type Gasometer interface {
CalculateGas(clientCtx gogogrpc.ClientConn, txf tx.Factory, msgs ...sdktypes.Msg) (*txtypes.SimulateResponse, uint64, error)
}

// Signer allows to mock the tx.Sign func.
type Signer interface {
Sign(txf tx.Factory, name string, txBuilder client.TxBuilder, overwriteSig bool) error
}

// Client is a client to access your chain by querying and broadcasting transactions.
type Client struct {
// RPC is Tendermint RPC.
Expand All @@ -87,6 +92,7 @@ type Client struct {
bankQueryClient banktypes.QueryClient
faucetClient FaucetClient
gasometer Gasometer
signer Signer

addressPrefix string

Expand Down Expand Up @@ -248,6 +254,14 @@ func WithGasometer(gasometer Gasometer) Option {
}
}

// WithSigner sets the signer.
// Already set by default.
func WithSigner(signer Signer) Option {
return func(c *Client) {
c.signer = signer
}
}

// New creates a new client with given options.
func New(ctx context.Context, options ...Option) (Client, error) {
c := Client{
Expand Down Expand Up @@ -322,6 +336,9 @@ func New(ctx context.Context, options ...Option) (Client, error) {
if c.gasometer == nil {
c.gasometer = gasometer{}
}
if c.signer == nil {
c.signer = signer{}
}
// set address prefix in SDK global config
c.SetConfigAddressPrefix()

Expand Down
167 changes: 68 additions & 99 deletions ignite/pkg/cosmosclient/cosmosclient_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,18 +20,25 @@ import (
"github.com/ignite/cli/ignite/pkg/cosmosfaucet"
)

const (
defaultFaucetDenom = "token"
defaultFaucetMinAmount = 100
)

//go:generate mockery --srcpkg github.com/tendermint/tendermint/rpc/client/ --name Client --structname RPCClient --filename rpclient.go --with-expecter
//go:generate mockery --srcpkg github.com/cosmos/cosmos-sdk/client --name AccountRetriever --filename account_retriever.go --with-expecter
//go:generate mockery --srcpkg github.com/cosmos/cosmos-sdk/x/bank/types --name QueryClient --structname BankQueryClient --filename bank_query_client.go --with-expecter
//go:generate mockery --srcpkg . --name FaucetClient --structname FaucetClient --filename faucet_client.go --with-expecter
//go:generate mockery --srcpkg . --name Gasometer --filename gasometer.go --with-expecter
//go:generate mockery --srcpkg . --name Signer --filename signer.go --with-expecter

type suite struct {
rpcClient *mocks.RPCClient
accountRetriever *mocks.AccountRetriever
bankQueryClient *mocks.BankQueryClient
gasometer *mocks.Gasometer
faucetClient *mocks.FaucetClient
signer *mocks.Signer
}

func newClient(t *testing.T, setup func(suite), opts ...cosmosclient.Option) cosmosclient.Client {
Expand All @@ -41,6 +48,7 @@ func newClient(t *testing.T, setup func(suite), opts ...cosmosclient.Option) cos
bankQueryClient: mocks.NewBankQueryClient(t),
gasometer: mocks.NewGasometer(t),
faucetClient: mocks.NewFaucetClient(t),
signer: mocks.NewSigner(t),
}
// Because rpcClient is passed as argument inside clientContext of mocked
// methods, we must EXPECT a call to String (because testify/mock is calling
Expand All @@ -61,6 +69,7 @@ func newClient(t *testing.T, setup func(suite), opts ...cosmosclient.Option) cos
cosmosclient.WithBankQueryClient(s.bankQueryClient),
cosmosclient.WithGasometer(s.gasometer),
cosmosclient.WithFaucetClient(s.faucetClient),
cosmosclient.WithSigner(s.signer),
}...)
c, err := cosmosclient.New(context.Background(), opts...)
require.NoError(t, err)
Expand Down Expand Up @@ -309,10 +318,6 @@ func TestClientStatus(t *testing.T) {
}

func TestClientCreateTx(t *testing.T) {
const (
defaultFaucetDenom = "token"
defaultFaucetMinAmount = 100
)
var (
accountName = "bob"
passphrase = "passphrase"
Expand Down Expand Up @@ -354,12 +359,7 @@ func TestClientCreateTx(t *testing.T) {
},
expectedJSONTx: `{"body":{"messages":[{"@type":"/cosmos.bank.v1beta1.MsgSend","from_address":"from","to_address":"to","amount":[{"denom":"token","amount":"1"}]}],"memo":"","timeout_height":"0","extension_options":[],"non_critical_extension_options":[]},"auth_info":{"signer_infos":[],"fee":{"amount":[],"gas_limit":"300000","payer":"","granter":""},"tip":null},"signatures":[]}`,
setup: func(s suite) {
s.accountRetriever.EXPECT().
EnsureExists(mock.Anything, sdkaddress).
Return(nil)
s.accountRetriever.EXPECT().
GetAccountNumberSequence(mock.Anything, sdkaddress).
Return(1, 2, nil)
s.expectPrepareFactory(sdkaddress)
},
},
{
Expand All @@ -376,26 +376,9 @@ func TestClientCreateTx(t *testing.T) {
},
expectedJSONTx: `{"body":{"messages":[{"@type":"/cosmos.bank.v1beta1.MsgSend","from_address":"from","to_address":"to","amount":[{"denom":"token","amount":"1"}]}],"memo":"","timeout_height":"0","extension_options":[],"non_critical_extension_options":[]},"auth_info":{"signer_infos":[],"fee":{"amount":[],"gas_limit":"300000","payer":"","granter":""},"tip":null},"signatures":[]}`,
setup: func(s suite) {
balance := sdktypes.NewCoin("token", sdktypes.NewIntFromUint64(defaultFaucetMinAmount))
s.bankQueryClient.EXPECT().Balance(
context.Background(),
&banktypes.QueryBalanceRequest{
Address: sdkaddress.String(),
Denom: defaultFaucetDenom,
},
).Return(
&banktypes.QueryBalanceResponse{
Balance: &balance,
},
nil,
)
s.expectMakeSureAccountHasToken(sdkaddress.String(), defaultFaucetMinAmount)

s.accountRetriever.EXPECT().
EnsureExists(mock.Anything, sdkaddress).
Return(nil)
s.accountRetriever.EXPECT().
GetAccountNumberSequence(mock.Anything, sdkaddress).
Return(1, 2, nil)
s.expectPrepareFactory(sdkaddress)
},
},
{
Expand All @@ -412,46 +395,8 @@ func TestClientCreateTx(t *testing.T) {
},
expectedJSONTx: `{"body":{"messages":[{"@type":"/cosmos.bank.v1beta1.MsgSend","from_address":"from","to_address":"to","amount":[{"denom":"token","amount":"1"}]}],"memo":"","timeout_height":"0","extension_options":[],"non_critical_extension_options":[]},"auth_info":{"signer_infos":[],"fee":{"amount":[],"gas_limit":"300000","payer":"","granter":""},"tip":null},"signatures":[]}`,
setup: func(s suite) {
balance := sdktypes.NewCoin("token", sdktypes.NewIntFromUint64(defaultFaucetMinAmount-1))
s.bankQueryClient.EXPECT().Balance(
context.Background(),
&banktypes.QueryBalanceRequest{
Address: sdkaddress.String(),
Denom: defaultFaucetDenom,
},
).Return(
&banktypes.QueryBalanceResponse{
Balance: &balance,
},
nil,
).Once()

s.faucetClient.EXPECT().Transfer(context.Background(),
cosmosfaucet.TransferRequest{AccountAddress: sdkaddress.String()},
).Return(
cosmosfaucet.TransferResponse{}, nil,
)

newBalance := sdktypes.NewCoin("token", sdktypes.NewIntFromUint64(defaultFaucetMinAmount))
s.bankQueryClient.EXPECT().Balance(
mock.Anything,
&banktypes.QueryBalanceRequest{
Address: sdkaddress.String(),
Denom: defaultFaucetDenom,
},
).Return(
&banktypes.QueryBalanceResponse{
Balance: &newBalance,
},
nil,
).Once()

s.accountRetriever.EXPECT().
EnsureExists(mock.Anything, sdkaddress).
Return(nil)
s.accountRetriever.EXPECT().
GetAccountNumberSequence(mock.Anything, sdkaddress).
Return(1, 2, nil)
s.expectMakeSureAccountHasToken(sdkaddress.String(), defaultFaucetMinAmount-1)
s.expectPrepareFactory(sdkaddress)
},
},
{
Expand All @@ -468,12 +413,7 @@ func TestClientCreateTx(t *testing.T) {
},
expectedJSONTx: `{"body":{"messages":[{"@type":"/cosmos.bank.v1beta1.MsgSend","from_address":"from","to_address":"to","amount":[{"denom":"token","amount":"1"}]}],"memo":"","timeout_height":"0","extension_options":[],"non_critical_extension_options":[]},"auth_info":{"signer_infos":[],"fee":{"amount":[{"denom":"token","amount":"10"}],"gas_limit":"300000","payer":"","granter":""},"tip":null},"signatures":[]}`,
setup: func(s suite) {
s.accountRetriever.EXPECT().
EnsureExists(mock.Anything, sdkaddress).
Return(nil)
s.accountRetriever.EXPECT().
GetAccountNumberSequence(mock.Anything, sdkaddress).
Return(1, 2, nil)
s.expectPrepareFactory(sdkaddress)
},
},
{
Expand All @@ -491,12 +431,7 @@ func TestClientCreateTx(t *testing.T) {
},
expectedJSONTx: `{"body":{"messages":[{"@type":"/cosmos.bank.v1beta1.MsgSend","from_address":"from","to_address":"to","amount":[{"denom":"token","amount":"1"}]}],"memo":"","timeout_height":"0","extension_options":[],"non_critical_extension_options":[]},"auth_info":{"signer_infos":[],"fee":{"amount":[{"denom":"token","amount":"900000"}],"gas_limit":"300000","payer":"","granter":""},"tip":null},"signatures":[]}`,
setup: func(s suite) {
s.accountRetriever.EXPECT().
EnsureExists(mock.Anything, sdkaddress).
Return(nil)
s.accountRetriever.EXPECT().
GetAccountNumberSequence(mock.Anything, sdkaddress).
Return(1, 2, nil)
s.expectPrepareFactory(sdkaddress)
},
},
{
Expand All @@ -514,12 +449,7 @@ func TestClientCreateTx(t *testing.T) {
},
expectedError: "cannot provide both fees and gas prices",
setup: func(s suite) {
s.accountRetriever.EXPECT().
EnsureExists(mock.Anything, sdkaddress).
Return(nil)
s.accountRetriever.EXPECT().
GetAccountNumberSequence(mock.Anything, sdkaddress).
Return(1, 2, nil)
s.expectPrepareFactory(sdkaddress)
},
},
{
Expand All @@ -536,12 +466,7 @@ func TestClientCreateTx(t *testing.T) {
},
expectedJSONTx: `{"body":{"messages":[{"@type":"/cosmos.bank.v1beta1.MsgSend","from_address":"from","to_address":"to","amount":[{"denom":"token","amount":"1"}]}],"memo":"","timeout_height":"0","extension_options":[],"non_critical_extension_options":[]},"auth_info":{"signer_infos":[],"fee":{"amount":[],"gas_limit":"20042","payer":"","granter":""},"tip":null},"signatures":[]}`,
setup: func(s suite) {
s.accountRetriever.EXPECT().
EnsureExists(mock.Anything, sdkaddress).
Return(nil)
s.accountRetriever.EXPECT().
GetAccountNumberSequence(mock.Anything, sdkaddress).
Return(1, 2, nil)
s.expectPrepareFactory(sdkaddress)
s.gasometer.EXPECT().
CalculateGas(mock.Anything, mock.Anything, mock.Anything).
Return(nil, 42, nil)
Expand All @@ -561,12 +486,7 @@ func TestClientCreateTx(t *testing.T) {
},
expectedJSONTx: `{"body":{"messages":[{"@type":"/cosmos.bank.v1beta1.MsgSend","from_address":"from","to_address":"to","amount":[{"denom":"token","amount":"1"}]}],"memo":"","timeout_height":"0","extension_options":[],"non_critical_extension_options":[]},"auth_info":{"signer_infos":[],"fee":{"amount":[],"gas_limit":"20042","payer":"","granter":""},"tip":null},"signatures":[]}`,
setup: func(s suite) {
s.accountRetriever.EXPECT().
EnsureExists(mock.Anything, sdkaddress).
Return(nil)
s.accountRetriever.EXPECT().
GetAccountNumberSequence(mock.Anything, sdkaddress).
Return(1, 2, nil)
s.expectPrepareFactory(sdkaddress)
s.gasometer.EXPECT().
CalculateGas(mock.Anything, mock.Anything, mock.Anything).
Return(nil, 42, nil)
Expand Down Expand Up @@ -598,3 +518,52 @@ func TestClientCreateTx(t *testing.T) {
})
}
}

func (s suite) expectMakeSureAccountHasToken(address string, balance int64) {
currentBalance := sdktypes.NewInt64Coin(defaultFaucetDenom, balance)
s.bankQueryClient.EXPECT().Balance(
context.Background(),
&banktypes.QueryBalanceRequest{
Address: address,
Denom: defaultFaucetDenom,
},
).Return(
&banktypes.QueryBalanceResponse{
Balance: &currentBalance,
},
nil,
).Once()
if balance >= defaultFaucetMinAmount {
// balance is high enought, faucet won't be called
return
}

s.faucetClient.EXPECT().Transfer(context.Background(),
cosmosfaucet.TransferRequest{AccountAddress: address},
).Return(
cosmosfaucet.TransferResponse{}, nil,
)

newBalance := sdktypes.NewInt64Coin(defaultFaucetDenom, defaultFaucetMinAmount)
s.bankQueryClient.EXPECT().Balance(
mock.Anything,
&banktypes.QueryBalanceRequest{
Address: address,
Denom: defaultFaucetDenom,
},
).Return(
&banktypes.QueryBalanceResponse{
Balance: &newBalance,
},
nil,
).Once()
}

func (s suite) expectPrepareFactory(sdkaddress sdktypes.Address) {
s.accountRetriever.EXPECT().
EnsureExists(mock.Anything, sdkaddress).
Return(nil)
s.accountRetriever.EXPECT().
GetAccountNumberSequence(mock.Anything, sdkaddress).
Return(1, 2, nil)
}
2 changes: 1 addition & 1 deletion ignite/pkg/cosmosclient/gasometer.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import (
gogogrpc "github.com/gogo/protobuf/grpc"
)

// Implements Gasometer
// gasometer implements the Gasometer interface
type gasometer struct{}

func (gasometer) CalculateGas(clientCtx gogogrpc.ClientConn, txf tx.Factory, msgs ...sdktypes.Msg) (*txtypes.SimulateResponse, uint64, error) {
Expand Down
Loading

0 comments on commit 529407a

Please sign in to comment.