testing(json rpc): Add backend test suite with mock grpc query client (#1199)

* tests(json-rpc): wip evm_backend unit test setup

* tests(json-rpc): wip evm_backend unit test setup

* fix viper

* wip query client mock

* fix first backend test except error message

* clean up

* wip Context with Height

* fix JSON RPC backend test setup

* typo

* refactor folder structure

* Update rpc/backend/evm_backend_test.go

Co-authored-by: Federico Kunze Küllmer <31522760+fedekunze@users.noreply.github.com>
This commit is contained in:
Daniel Burckhardt 2022-07-23 11:16:23 +02:00 committed by GitHub
parent cd41c48075
commit c626a5c8ee
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 497 additions and 0 deletions

1
go.mod
View File

@ -133,6 +133,7 @@ require (
github.com/spf13/afero v1.8.2 // indirect github.com/spf13/afero v1.8.2 // indirect
github.com/spf13/jwalterweatherman v1.1.0 // indirect github.com/spf13/jwalterweatherman v1.1.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect github.com/spf13/pflag v1.0.5 // indirect
github.com/stretchr/objx v0.4.0 // indirect
github.com/subosito/gotenv v1.3.0 // indirect github.com/subosito/gotenv v1.3.0 // indirect
github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 // indirect github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 // indirect
github.com/tendermint/btcd v0.1.1 // indirect github.com/tendermint/btcd v0.1.1 // indirect

View File

@ -0,0 +1,73 @@
package backend
import (
"testing"
"github.com/cosmos/cosmos-sdk/client"
"github.com/cosmos/cosmos-sdk/server"
grpctypes "github.com/cosmos/cosmos-sdk/types/grpc"
mock "github.com/stretchr/testify/mock"
"github.com/stretchr/testify/require"
"github.com/stretchr/testify/suite"
"google.golang.org/grpc"
"google.golang.org/grpc/metadata"
"github.com/evmos/ethermint/rpc/backend/mocks"
rpc "github.com/evmos/ethermint/rpc/types"
evmtypes "github.com/evmos/ethermint/x/evm/types"
)
type BackendTestSuite struct {
suite.Suite
backend *Backend
}
func TestBackendTestSuite(t *testing.T) {
suite.Run(t, new(BackendTestSuite))
}
// SetupTest is executed before every BackendTestSuite test
func (suite *BackendTestSuite) SetupTest() {
ctx := server.NewDefaultContext()
ctx.Viper.Set("telemetry.global-labels", []interface{}{})
clientCtx := client.Context{}.WithChainID("ethermint_9000-1").WithHeight(1)
allowUnprotectedTxs := false
suite.backend = NewBackend(ctx, ctx.Logger, clientCtx, allowUnprotectedTxs)
queryClient := mocks.NewQueryClient(suite.T())
var header metadata.MD
RegisterMockQueries(queryClient, &header)
suite.backend.queryClient.QueryClient = queryClient
suite.backend.ctx = rpc.ContextWithHeight(1)
}
// QueryClient defines a mocked object that implements the grpc QueryCLient
// interface. It's used on tests to test the JSON-RPC without running a grpc
// client server. E.g. JSON-PRC-CLIENT -> BACKEND -> Mock GRPC CLIENT -> APP
var _ evmtypes.QueryClient = &mocks.QueryClient{}
// RegisterMockQueries registers the queries and their respective responses,
// so that they can be called in tests using the queryClient
func RegisterMockQueries(queryClient *mocks.QueryClient, header *metadata.MD) {
queryClient.On("Params", rpc.ContextWithHeight(1), &evmtypes.QueryParamsRequest{}, grpc.Header(header)).
Return(&evmtypes.QueryParamsResponse{}, nil).
Run(func(args mock.Arguments) {
// If Params call is successful, also update the header height
arg := args.Get(2).(grpc.HeaderCallOption)
h := metadata.MD{}
h.Set(grpctypes.GRPCBlockHeightHeader, "1")
*arg.HeaderAddr = h
})
}
func TestQueryClient(t *testing.T) {
queryClient := mocks.NewQueryClient(t)
var header metadata.MD
RegisterMockQueries(queryClient, &header)
// mock calls for abstraction
_, err := queryClient.Params(rpc.ContextWithHeight(1), &evmtypes.QueryParamsRequest{}, grpc.Header(&header))
require.NoError(t, err)
}

View File

@ -0,0 +1,30 @@
package backend
import "github.com/ethereum/go-ethereum/common/hexutil"
func (suite *BackendTestSuite) TestBlockNumber() {
testCases := []struct {
mame string
malleate func()
expBlockNumber hexutil.Uint64
expPass bool
}{
{
"pass",
func() {
},
0x1,
true,
},
}
for _, tc := range testCases {
blockNumber, err := suite.backend.BlockNumber()
if tc.expPass {
suite.Require().NoError(err)
suite.Require().Equal(tc.expBlockNumber, blockNumber)
} else {
suite.Require().Error(err)
}
}
}

View File

@ -0,0 +1,393 @@
// Code generated by mockery v2.14.0. DO NOT EDIT.
package mocks
import (
context "context"
grpc "google.golang.org/grpc"
mock "github.com/stretchr/testify/mock"
types "github.com/evmos/ethermint/x/evm/types"
)
// QueryClient is an autogenerated mock type for the QueryClient type
type QueryClient struct {
mock.Mock
}
// Account provides a mock function with given fields: ctx, in, opts
func (_m *QueryClient) Account(ctx context.Context, in *types.QueryAccountRequest, opts ...grpc.CallOption) (*types.QueryAccountResponse, error) {
_va := make([]interface{}, len(opts))
for _i := range opts {
_va[_i] = opts[_i]
}
var _ca []interface{}
_ca = append(_ca, ctx, in)
_ca = append(_ca, _va...)
ret := _m.Called(_ca...)
var r0 *types.QueryAccountResponse
if rf, ok := ret.Get(0).(func(context.Context, *types.QueryAccountRequest, ...grpc.CallOption) *types.QueryAccountResponse); ok {
r0 = rf(ctx, in, opts...)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*types.QueryAccountResponse)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(context.Context, *types.QueryAccountRequest, ...grpc.CallOption) error); ok {
r1 = rf(ctx, in, opts...)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// Balance provides a mock function with given fields: ctx, in, opts
func (_m *QueryClient) Balance(ctx context.Context, in *types.QueryBalanceRequest, opts ...grpc.CallOption) (*types.QueryBalanceResponse, error) {
_va := make([]interface{}, len(opts))
for _i := range opts {
_va[_i] = opts[_i]
}
var _ca []interface{}
_ca = append(_ca, ctx, in)
_ca = append(_ca, _va...)
ret := _m.Called(_ca...)
var r0 *types.QueryBalanceResponse
if rf, ok := ret.Get(0).(func(context.Context, *types.QueryBalanceRequest, ...grpc.CallOption) *types.QueryBalanceResponse); ok {
r0 = rf(ctx, in, opts...)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*types.QueryBalanceResponse)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(context.Context, *types.QueryBalanceRequest, ...grpc.CallOption) error); ok {
r1 = rf(ctx, in, opts...)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// BaseFee provides a mock function with given fields: ctx, in, opts
func (_m *QueryClient) BaseFee(ctx context.Context, in *types.QueryBaseFeeRequest, opts ...grpc.CallOption) (*types.QueryBaseFeeResponse, error) {
_va := make([]interface{}, len(opts))
for _i := range opts {
_va[_i] = opts[_i]
}
var _ca []interface{}
_ca = append(_ca, ctx, in)
_ca = append(_ca, _va...)
ret := _m.Called(_ca...)
var r0 *types.QueryBaseFeeResponse
if rf, ok := ret.Get(0).(func(context.Context, *types.QueryBaseFeeRequest, ...grpc.CallOption) *types.QueryBaseFeeResponse); ok {
r0 = rf(ctx, in, opts...)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*types.QueryBaseFeeResponse)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(context.Context, *types.QueryBaseFeeRequest, ...grpc.CallOption) error); ok {
r1 = rf(ctx, in, opts...)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// Code provides a mock function with given fields: ctx, in, opts
func (_m *QueryClient) Code(ctx context.Context, in *types.QueryCodeRequest, opts ...grpc.CallOption) (*types.QueryCodeResponse, error) {
_va := make([]interface{}, len(opts))
for _i := range opts {
_va[_i] = opts[_i]
}
var _ca []interface{}
_ca = append(_ca, ctx, in)
_ca = append(_ca, _va...)
ret := _m.Called(_ca...)
var r0 *types.QueryCodeResponse
if rf, ok := ret.Get(0).(func(context.Context, *types.QueryCodeRequest, ...grpc.CallOption) *types.QueryCodeResponse); ok {
r0 = rf(ctx, in, opts...)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*types.QueryCodeResponse)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(context.Context, *types.QueryCodeRequest, ...grpc.CallOption) error); ok {
r1 = rf(ctx, in, opts...)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// CosmosAccount provides a mock function with given fields: ctx, in, opts
func (_m *QueryClient) CosmosAccount(ctx context.Context, in *types.QueryCosmosAccountRequest, opts ...grpc.CallOption) (*types.QueryCosmosAccountResponse, error) {
_va := make([]interface{}, len(opts))
for _i := range opts {
_va[_i] = opts[_i]
}
var _ca []interface{}
_ca = append(_ca, ctx, in)
_ca = append(_ca, _va...)
ret := _m.Called(_ca...)
var r0 *types.QueryCosmosAccountResponse
if rf, ok := ret.Get(0).(func(context.Context, *types.QueryCosmosAccountRequest, ...grpc.CallOption) *types.QueryCosmosAccountResponse); ok {
r0 = rf(ctx, in, opts...)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*types.QueryCosmosAccountResponse)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(context.Context, *types.QueryCosmosAccountRequest, ...grpc.CallOption) error); ok {
r1 = rf(ctx, in, opts...)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// EstimateGas provides a mock function with given fields: ctx, in, opts
func (_m *QueryClient) EstimateGas(ctx context.Context, in *types.EthCallRequest, opts ...grpc.CallOption) (*types.EstimateGasResponse, error) {
_va := make([]interface{}, len(opts))
for _i := range opts {
_va[_i] = opts[_i]
}
var _ca []interface{}
_ca = append(_ca, ctx, in)
_ca = append(_ca, _va...)
ret := _m.Called(_ca...)
var r0 *types.EstimateGasResponse
if rf, ok := ret.Get(0).(func(context.Context, *types.EthCallRequest, ...grpc.CallOption) *types.EstimateGasResponse); ok {
r0 = rf(ctx, in, opts...)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*types.EstimateGasResponse)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(context.Context, *types.EthCallRequest, ...grpc.CallOption) error); ok {
r1 = rf(ctx, in, opts...)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// EthCall provides a mock function with given fields: ctx, in, opts
func (_m *QueryClient) EthCall(ctx context.Context, in *types.EthCallRequest, opts ...grpc.CallOption) (*types.MsgEthereumTxResponse, error) {
_va := make([]interface{}, len(opts))
for _i := range opts {
_va[_i] = opts[_i]
}
var _ca []interface{}
_ca = append(_ca, ctx, in)
_ca = append(_ca, _va...)
ret := _m.Called(_ca...)
var r0 *types.MsgEthereumTxResponse
if rf, ok := ret.Get(0).(func(context.Context, *types.EthCallRequest, ...grpc.CallOption) *types.MsgEthereumTxResponse); ok {
r0 = rf(ctx, in, opts...)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*types.MsgEthereumTxResponse)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(context.Context, *types.EthCallRequest, ...grpc.CallOption) error); ok {
r1 = rf(ctx, in, opts...)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// Params provides a mock function with given fields: ctx, in, opts
func (_m *QueryClient) Params(ctx context.Context, in *types.QueryParamsRequest, opts ...grpc.CallOption) (*types.QueryParamsResponse, error) {
_va := make([]interface{}, len(opts))
for _i := range opts {
_va[_i] = opts[_i]
}
var _ca []interface{}
_ca = append(_ca, ctx, in)
_ca = append(_ca, _va...)
ret := _m.Called(_ca...)
var r0 *types.QueryParamsResponse
if rf, ok := ret.Get(0).(func(context.Context, *types.QueryParamsRequest, ...grpc.CallOption) *types.QueryParamsResponse); ok {
r0 = rf(ctx, in, opts...)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*types.QueryParamsResponse)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(context.Context, *types.QueryParamsRequest, ...grpc.CallOption) error); ok {
r1 = rf(ctx, in, opts...)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// Storage provides a mock function with given fields: ctx, in, opts
func (_m *QueryClient) Storage(ctx context.Context, in *types.QueryStorageRequest, opts ...grpc.CallOption) (*types.QueryStorageResponse, error) {
_va := make([]interface{}, len(opts))
for _i := range opts {
_va[_i] = opts[_i]
}
var _ca []interface{}
_ca = append(_ca, ctx, in)
_ca = append(_ca, _va...)
ret := _m.Called(_ca...)
var r0 *types.QueryStorageResponse
if rf, ok := ret.Get(0).(func(context.Context, *types.QueryStorageRequest, ...grpc.CallOption) *types.QueryStorageResponse); ok {
r0 = rf(ctx, in, opts...)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*types.QueryStorageResponse)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(context.Context, *types.QueryStorageRequest, ...grpc.CallOption) error); ok {
r1 = rf(ctx, in, opts...)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// TraceBlock provides a mock function with given fields: ctx, in, opts
func (_m *QueryClient) TraceBlock(ctx context.Context, in *types.QueryTraceBlockRequest, opts ...grpc.CallOption) (*types.QueryTraceBlockResponse, error) {
_va := make([]interface{}, len(opts))
for _i := range opts {
_va[_i] = opts[_i]
}
var _ca []interface{}
_ca = append(_ca, ctx, in)
_ca = append(_ca, _va...)
ret := _m.Called(_ca...)
var r0 *types.QueryTraceBlockResponse
if rf, ok := ret.Get(0).(func(context.Context, *types.QueryTraceBlockRequest, ...grpc.CallOption) *types.QueryTraceBlockResponse); ok {
r0 = rf(ctx, in, opts...)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*types.QueryTraceBlockResponse)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(context.Context, *types.QueryTraceBlockRequest, ...grpc.CallOption) error); ok {
r1 = rf(ctx, in, opts...)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// TraceTx provides a mock function with given fields: ctx, in, opts
func (_m *QueryClient) TraceTx(ctx context.Context, in *types.QueryTraceTxRequest, opts ...grpc.CallOption) (*types.QueryTraceTxResponse, error) {
_va := make([]interface{}, len(opts))
for _i := range opts {
_va[_i] = opts[_i]
}
var _ca []interface{}
_ca = append(_ca, ctx, in)
_ca = append(_ca, _va...)
ret := _m.Called(_ca...)
var r0 *types.QueryTraceTxResponse
if rf, ok := ret.Get(0).(func(context.Context, *types.QueryTraceTxRequest, ...grpc.CallOption) *types.QueryTraceTxResponse); ok {
r0 = rf(ctx, in, opts...)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*types.QueryTraceTxResponse)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(context.Context, *types.QueryTraceTxRequest, ...grpc.CallOption) error); ok {
r1 = rf(ctx, in, opts...)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// ValidatorAccount provides a mock function with given fields: ctx, in, opts
func (_m *QueryClient) ValidatorAccount(ctx context.Context, in *types.QueryValidatorAccountRequest, opts ...grpc.CallOption) (*types.QueryValidatorAccountResponse, error) {
_va := make([]interface{}, len(opts))
for _i := range opts {
_va[_i] = opts[_i]
}
var _ca []interface{}
_ca = append(_ca, ctx, in)
_ca = append(_ca, _va...)
ret := _m.Called(_ca...)
var r0 *types.QueryValidatorAccountResponse
if rf, ok := ret.Get(0).(func(context.Context, *types.QueryValidatorAccountRequest, ...grpc.CallOption) *types.QueryValidatorAccountResponse); ok {
r0 = rf(ctx, in, opts...)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*types.QueryValidatorAccountResponse)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(context.Context, *types.QueryValidatorAccountRequest, ...grpc.CallOption) error); ok {
r1 = rf(ctx, in, opts...)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
type mockConstructorTestingTNewQueryClient interface {
mock.TestingT
Cleanup(func())
}
// NewQueryClient creates a new instance of QueryClient. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
func NewQueryClient(t mockConstructorTestingTNewQueryClient) *QueryClient {
mock := &QueryClient{}
mock.Mock.Test(t)
t.Cleanup(func() { mock.AssertExpectations(t) })
return mock
}