Skip to content

Commit

Permalink
refactor(tests): Port auth integration tests to server v2 (#22554)
Browse files Browse the repository at this point in the history
  • Loading branch information
sontrinh16 authored Nov 27, 2024
1 parent f296a50 commit 5048f26
Show file tree
Hide file tree
Showing 7 changed files with 764 additions and 3 deletions.
54 changes: 51 additions & 3 deletions runtime/v2/module.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,12 @@ import (
autocliv1 "cosmossdk.io/api/cosmos/autocli/v1"
reflectionv1 "cosmossdk.io/api/cosmos/reflection/v1"
appmodulev2 "cosmossdk.io/core/appmodule/v2"
"cosmossdk.io/core/branch"
"cosmossdk.io/core/comet"
"cosmossdk.io/core/event"
"cosmossdk.io/core/header"
"cosmossdk.io/core/registry"
"cosmossdk.io/core/router"
"cosmossdk.io/core/store"
"cosmossdk.io/core/transaction"
"cosmossdk.io/depinject"
Expand Down Expand Up @@ -213,27 +215,66 @@ func ProvideEnvironment(
memKvService store.MemoryStoreService,
headerService header.Service,
eventService event.Service,
branchService branch.Service,
routerBuilder RouterServiceBuilder,
) appmodulev2.Environment {
return appmodulev2.Environment{
Logger: logger,
BranchService: stf.BranchService{},
BranchService: branchService,
EventService: eventService,
GasService: stf.NewGasMeterService(),
HeaderService: headerService,
QueryRouterService: stf.NewQueryRouterService(),
MsgRouterService: stf.NewMsgRouterService([]byte(key.Name())),
QueryRouterService: routerBuilder.BuildQueryRouter(),
MsgRouterService: routerBuilder.BuildMsgRouter([]byte(key.Name())),
TransactionService: services.NewContextAwareTransactionService(),
KVStoreService: kvService,
MemStoreService: memKvService,
}
}

// RouterServiceBuilder builds the msg router and query router service during app initialization.
// this is mainly use for testing to override message router service in the environment and not in stf.
type RouterServiceBuilder interface {
// BuildMsgRouter return a msg router service.
// - actor is the module store key.
BuildMsgRouter(actor []byte) router.Service
BuildQueryRouter() router.Service
}

type RouterServiceFactory func([]byte) router.Service

// routerBuilder implements RouterServiceBuilder
type routerBuilder struct {
msgRouterServiceFactory RouterServiceFactory
queryRouter router.Service
}

func NewRouterBuilder(
msgRouterServiceFactory RouterServiceFactory,
queryRouter router.Service,
) RouterServiceBuilder {
return routerBuilder{
msgRouterServiceFactory: msgRouterServiceFactory,
queryRouter: queryRouter,
}
}

func (b routerBuilder) BuildMsgRouter(actor []byte) router.Service {
return b.msgRouterServiceFactory(actor)
}

func (b routerBuilder) BuildQueryRouter() router.Service {
return b.queryRouter
}

// DefaultServiceBindings provides default services for the following service interfaces:
// - store.KVStoreServiceFactory
// - header.Service
// - comet.Service
// - event.Service
// - store/v2/root.Builder
// - branch.Service
// - RouterServiceBuilder
//
// They are all required. For most use cases these default services bindings should be sufficient.
// Power users (or tests) may wish to provide their own services bindings, in which case they must
Expand All @@ -246,16 +287,23 @@ func DefaultServiceBindings() depinject.Config {
stf.NewKVStoreService(actor),
)
}
routerBuilder RouterServiceBuilder = routerBuilder{
msgRouterServiceFactory: stf.NewMsgRouterService,
queryRouter: stf.NewQueryRouterService(),
}
cometService comet.Service = &services.ContextAwareCometInfoService{}
headerService = services.NewGenesisHeaderService(stf.HeaderService{})
eventService = services.NewGenesisEventService(stf.NewEventService())
storeBuilder = root.NewBuilder()
branchService = stf.BranchService{}
)
return depinject.Supply(
kvServiceFactory,
routerBuilder,
headerService,
cometService,
eventService,
storeBuilder,
branchService,
)
}
55 changes: 55 additions & 0 deletions tests/integration/v2/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import (
cmttypes "github.com/cometbft/cometbft/types"
"github.com/stretchr/testify/require"

corebranch "cosmossdk.io/core/branch"
"cosmossdk.io/core/comet"
corecontext "cosmossdk.io/core/context"
"cosmossdk.io/core/server"
Expand Down Expand Up @@ -53,6 +54,8 @@ const (

type stateMachineTx = transaction.Tx

type handler = func(ctx context.Context) (transaction.Msg, error)

// DefaultConsensusParams defines the default CometBFT consensus params used in
// SimApp testing.
var DefaultConsensusParams = &cmtproto.ConsensusParams{
Expand Down Expand Up @@ -88,6 +91,11 @@ type StartupConfig struct {
GenesisAccounts []GenesisAccount
// HomeDir defines the home directory of the app where config and data will be stored.
HomeDir string
// BranchService defines the custom branch service to be used in the app.
BranchService corebranch.Service
// RouterServiceBuilder defines the custom builder
// for msg router and query router service to be used in the app.
RouterServiceBuilder runtime.RouterServiceBuilder
}

func DefaultStartUpConfig(t *testing.T) StartupConfig {
Expand All @@ -113,6 +121,26 @@ func DefaultStartUpConfig(t *testing.T) StartupConfig {
GenesisBehavior: Genesis_COMMIT,
GenesisAccounts: []GenesisAccount{ga},
HomeDir: homedir,
BranchService: stf.BranchService{},
RouterServiceBuilder: runtime.NewRouterBuilder(
stf.NewMsgRouterService, stf.NewQueryRouterService(),
),
}
}

// RunMsgConfig defines the run message configuration.
type RunMsgConfig struct {
Commit bool
}

// Option is a function that can be used to configure the integration app.
type Option func(*RunMsgConfig)

// WithAutomaticCommit enables automatic commit.
// This means that the integration app will automatically commit the state after each msg.
func WithAutomaticCommit() Option {
return func(cfg *RunMsgConfig) {
cfg.Commit = true
}
}

Expand Down Expand Up @@ -159,6 +187,8 @@ func NewApp(
kvFactory,
&eventService{},
storeBuilder,
startupConfig.BranchService,
startupConfig.RouterServiceBuilder,
),
depinject.Invoke(
std.RegisterInterfaces,
Expand Down Expand Up @@ -397,6 +427,31 @@ func (a *App) SignCheckDeliver(
return txResult
}

// RunMsg runs the handler for a transaction message.
// It required the context to have the integration context.
// a new state is committed if the option WithAutomaticCommit is set in options.
func (app *App) RunMsg(t *testing.T, ctx context.Context, handler handler, option ...Option) (resp transaction.Msg, err error) {
// set options
cfg := &RunMsgConfig{}
for _, opt := range option {
opt(cfg)
}

// need to have integration context
integrationCtx, ok := ctx.Value(contextKey).(*integrationContext)
require.True(t, ok)

resp, err = handler(ctx)

if cfg.Commit {
app.lastHeight++
_, err := app.Commit(integrationCtx.state)
require.NoError(t, err)
}

return resp, err
}

// CheckBalance checks the balance of the given address.
func (a *App) CheckBalance(
t *testing.T, ctx context.Context, addr sdk.AccAddress, expected sdk.Coins, keeper bankkeeper.Keeper,
Expand Down
185 changes: 185 additions & 0 deletions tests/integration/v2/auth/accounts_retro_compatibility_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
package auth

import (
"context"
"errors"
"testing"

gogotypes "github.com/cosmos/gogoproto/types"
"github.com/stretchr/testify/require"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"

"cosmossdk.io/x/accounts/accountstd"
basev1 "cosmossdk.io/x/accounts/defaults/base/v1"

codectypes "github.com/cosmos/cosmos-sdk/codec/types"
"github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1"
authkeeper "github.com/cosmos/cosmos-sdk/x/auth/keeper"
authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
)

var _ accountstd.Interface = mockRetroCompatAccount{}

type mockRetroCompatAccount struct {
retroCompat *authtypes.QueryLegacyAccountResponse
address []byte
}

var (
valid = &mockRetroCompatAccount{
retroCompat: &authtypes.QueryLegacyAccountResponse{
Account: &codectypes.Any{},
Base: &authtypes.BaseAccount{
Address: "test",
PubKey: nil,
AccountNumber: 10,
Sequence: 20,
},
},
}

noInfo = &mockRetroCompatAccount{
retroCompat: &authtypes.QueryLegacyAccountResponse{
Account: &codectypes.Any{},
},
}
noImplement = &mockRetroCompatAccount{
retroCompat: nil,
}
)

func newMockRetroCompatAccount(name string, acc accountstd.Interface) accountstd.AccountCreatorFunc {
return func(_ accountstd.Dependencies) (string, accountstd.Interface, error) {
_, ok := acc.(*mockRetroCompatAccount)
if !ok {
return name, nil, errors.New("invalid account type")
}
return name, acc, nil
}
}

func ProvideMockRetroCompatAccountValid() accountstd.DepinjectAccount {
return accountstd.DepinjectAccount{MakeAccount: newMockRetroCompatAccount("valid", valid)}
}

func ProvideMockRetroCompatAccountNoInfo() accountstd.DepinjectAccount {
return accountstd.DepinjectAccount{MakeAccount: newMockRetroCompatAccount("no_info", noInfo)}
}

func ProvideMockRetroCompatAccountNoImplement() accountstd.DepinjectAccount {
return accountstd.DepinjectAccount{MakeAccount: newMockRetroCompatAccount("no_implement", noImplement)}
}

func (m mockRetroCompatAccount) RegisterInitHandler(builder *accountstd.InitBuilder) {
accountstd.RegisterInitHandler(builder, func(ctx context.Context, req *gogotypes.Empty) (*gogotypes.Empty, error) {
return &gogotypes.Empty{}, nil
})
}

func (m mockRetroCompatAccount) RegisterExecuteHandlers(_ *accountstd.ExecuteBuilder) {}

func (m mockRetroCompatAccount) RegisterQueryHandlers(builder *accountstd.QueryBuilder) {
if m.retroCompat == nil {
return
}
accountstd.RegisterQueryHandler(builder, func(ctx context.Context, req *authtypes.QueryLegacyAccount) (*authtypes.QueryLegacyAccountResponse, error) {
return m.retroCompat, nil
})
}

func TestAuthToAccountsGRPCCompat(t *testing.T) {
accs := map[string]accountstd.Interface{
"valid": valid,
"no_info": noInfo,
"no_implement": noImplement,
}

f := createTestSuite(t)

// init three accounts
for n, a := range accs {
_, addr, err := f.accountsKeeper.Init(f.ctx, n, []byte("me"), &gogotypes.Empty{}, nil)
require.NoError(t, err)
a.(*mockRetroCompatAccount).address = addr
}

qs := authkeeper.NewQueryServer(f.authKeeper)

t.Run("account supports info and account query", func(t *testing.T) {
infoResp, err := qs.AccountInfo(f.ctx, &authtypes.QueryAccountInfoRequest{
Address: f.mustAddr(valid.address),
})
require.NoError(t, err)
require.Equal(t, infoResp.Info, valid.retroCompat.Base)

accountResp, err := qs.Account(f.ctx, &authtypes.QueryAccountRequest{
Address: f.mustAddr(noInfo.address),
})
require.NoError(t, err)
require.Equal(t, accountResp.Account, valid.retroCompat.Account)
})

t.Run("account only supports account query, not info", func(t *testing.T) {
_, err := qs.AccountInfo(f.ctx, &authtypes.QueryAccountInfoRequest{
Address: f.mustAddr(noInfo.address),
})
require.Error(t, err)
require.Equal(t, status.Code(err), codes.NotFound)

resp, err := qs.Account(f.ctx, &authtypes.QueryAccountRequest{
Address: f.mustAddr(noInfo.address),
})
require.NoError(t, err)
require.Equal(t, resp.Account, valid.retroCompat.Account)
})

t.Run("account does not support any retro compat", func(t *testing.T) {
_, err := qs.AccountInfo(f.ctx, &authtypes.QueryAccountInfoRequest{
Address: f.mustAddr(noImplement.address),
})
require.Error(t, err)
require.Equal(t, status.Code(err), codes.NotFound)

_, err = qs.Account(f.ctx, &authtypes.QueryAccountRequest{
Address: f.mustAddr(noImplement.address),
})

require.Error(t, err)
require.Equal(t, status.Code(err), codes.NotFound)
})
}

func TestAccountsBaseAccountRetroCompat(t *testing.T) {
f := createTestSuite(t)
// init a base acc
anyPk, err := codectypes.NewAnyWithValue(secp256k1.GenPrivKey().PubKey())
require.NoError(t, err)

// we init two accounts. Account number should start with 4
// since the first three accounts are fee_collector, bonded_tokens_pool, not_bonded_tokens_pool
// generated by init genesis plus one more genesis account, which make the current account number 4.
_, _, err = f.accountsKeeper.Init(f.ctx, "base", []byte("me"), &basev1.MsgInit{PubKey: anyPk}, nil)
require.NoError(t, err)

_, addr, err := f.accountsKeeper.Init(f.ctx, "base", []byte("me"), &basev1.MsgInit{PubKey: anyPk}, nil)
require.NoError(t, err)

// try to query it via auth
qs := authkeeper.NewQueryServer(f.authKeeper)

r, err := qs.Account(f.ctx, &authtypes.QueryAccountRequest{
Address: f.mustAddr(addr),
})
require.NoError(t, err)
require.NotNil(t, r.Account)

info, err := qs.AccountInfo(f.ctx, &authtypes.QueryAccountInfoRequest{
Address: f.mustAddr(addr),
})
require.NoError(t, err)
require.NotNil(t, info.Info)
require.Equal(t, info.Info.PubKey, anyPk)
// Account number should be 5
require.Equal(t, info.Info.AccountNumber, uint64(5))
}
Loading

0 comments on commit 5048f26

Please sign in to comment.