github.com/cosmos/cosmos-sdk@v0.50.10/types/query/pagination_test.go (about)

     1  package query_test
     2  
     3  import (
     4  	gocontext "context"
     5  	"fmt"
     6  	"testing"
     7  
     8  	cmtproto "github.com/cometbft/cometbft/proto/tendermint/types"
     9  	"github.com/stretchr/testify/suite"
    10  
    11  	"cosmossdk.io/depinject"
    12  	"cosmossdk.io/log"
    13  	"cosmossdk.io/math"
    14  	"cosmossdk.io/store/prefix"
    15  
    16  	"github.com/cosmos/cosmos-sdk/baseapp"
    17  	"github.com/cosmos/cosmos-sdk/codec"
    18  	codectypes "github.com/cosmos/cosmos-sdk/codec/types"
    19  	"github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1"
    20  	"github.com/cosmos/cosmos-sdk/runtime"
    21  	"github.com/cosmos/cosmos-sdk/testutil/configurator"
    22  	testutilsims "github.com/cosmos/cosmos-sdk/testutil/sims"
    23  	sdk "github.com/cosmos/cosmos-sdk/types"
    24  	"github.com/cosmos/cosmos-sdk/types/address"
    25  	"github.com/cosmos/cosmos-sdk/types/query"
    26  	_ "github.com/cosmos/cosmos-sdk/x/auth"
    27  	authkeeper "github.com/cosmos/cosmos-sdk/x/auth/keeper"
    28  	_ "github.com/cosmos/cosmos-sdk/x/bank"
    29  	bankkeeper "github.com/cosmos/cosmos-sdk/x/bank/keeper"
    30  	"github.com/cosmos/cosmos-sdk/x/bank/testutil"
    31  	"github.com/cosmos/cosmos-sdk/x/bank/types"
    32  	_ "github.com/cosmos/cosmos-sdk/x/consensus"
    33  	_ "github.com/cosmos/cosmos-sdk/x/params"
    34  )
    35  
    36  const (
    37  	holder          = "holder"
    38  	multiPerm       = "multiple permissions account"
    39  	randomPerm      = "random permission"
    40  	numBalances     = 235
    41  	defaultLimit    = 100
    42  	overLimit       = 101
    43  	underLimit      = 10
    44  	lastPageRecords = 35
    45  )
    46  
    47  type paginationTestSuite struct {
    48  	suite.Suite
    49  	ctx           sdk.Context
    50  	bankKeeper    bankkeeper.Keeper
    51  	accountKeeper authkeeper.AccountKeeper
    52  	cdc           codec.Codec
    53  	interfaceReg  codectypes.InterfaceRegistry
    54  	app           *runtime.App
    55  }
    56  
    57  func TestPaginationTestSuite(t *testing.T) {
    58  	suite.Run(t, new(paginationTestSuite))
    59  }
    60  
    61  func (s *paginationTestSuite) SetupTest() {
    62  	var (
    63  		bankKeeper    bankkeeper.Keeper
    64  		accountKeeper authkeeper.AccountKeeper
    65  		reg           codectypes.InterfaceRegistry
    66  		cdc           codec.Codec
    67  	)
    68  
    69  	app, err := testutilsims.Setup(
    70  		depinject.Configs(
    71  			configurator.NewAppConfig(
    72  				configurator.AuthModule(),
    73  				configurator.BankModule(),
    74  				configurator.ParamsModule(),
    75  				configurator.ConsensusModule(),
    76  				configurator.OmitInitGenesis(),
    77  			),
    78  			depinject.Supply(log.NewNopLogger()),
    79  		),
    80  		&bankKeeper, &accountKeeper, &reg, &cdc)
    81  
    82  	s.NoError(err)
    83  
    84  	ctx := app.BaseApp.NewContextLegacy(false, cmtproto.Header{Height: 1})
    85  
    86  	s.ctx, s.bankKeeper, s.accountKeeper, s.cdc, s.app, s.interfaceReg = ctx, bankKeeper, accountKeeper, cdc, app, reg
    87  }
    88  
    89  func (s *paginationTestSuite) TestParsePagination() {
    90  	s.T().Log("verify default values for empty page request")
    91  	pageReq := &query.PageRequest{}
    92  	page, limit, err := query.ParsePagination(pageReq)
    93  	s.Require().NoError(err)
    94  	s.Require().Equal(limit, query.DefaultLimit)
    95  	s.Require().Equal(page, 1)
    96  
    97  	s.T().Log("verify with custom values")
    98  	pageReq = &query.PageRequest{
    99  		Offset: 0,
   100  		Limit:  10,
   101  	}
   102  	page, limit, err = query.ParsePagination(pageReq)
   103  	s.Require().NoError(err)
   104  	s.Require().Equal(page, 1)
   105  	s.Require().Equal(limit, 10)
   106  }
   107  
   108  func (s *paginationTestSuite) TestPagination() {
   109  	queryHelper := baseapp.NewQueryServerTestHelper(s.ctx, s.interfaceReg)
   110  	types.RegisterQueryServer(queryHelper, s.bankKeeper)
   111  	queryClient := types.NewQueryClient(queryHelper)
   112  
   113  	var balances sdk.Coins
   114  
   115  	for i := 0; i < numBalances; i++ {
   116  		denom := fmt.Sprintf("foo%ddenom", i)
   117  		balances = append(balances, sdk.NewInt64Coin(denom, 100))
   118  	}
   119  
   120  	balances = balances.Sort()
   121  	addr1 := sdk.AccAddress(secp256k1.GenPrivKey().PubKey().Address())
   122  	acc1 := s.accountKeeper.NewAccountWithAddress(s.ctx, addr1)
   123  	s.accountKeeper.SetAccount(s.ctx, acc1)
   124  	s.Require().NoError(testutil.FundAccount(s.ctx, s.bankKeeper, addr1, balances))
   125  
   126  	s.T().Log("verify empty page request results a max of defaultLimit records and counts total records")
   127  	pageReq := &query.PageRequest{}
   128  	request := types.NewQueryAllBalancesRequest(addr1, pageReq, false)
   129  	res, err := queryClient.AllBalances(gocontext.Background(), request)
   130  	s.Require().NoError(err)
   131  	s.Require().Equal(res.Pagination.Total, uint64(numBalances))
   132  	s.Require().NotNil(res.Pagination.NextKey)
   133  	s.Require().LessOrEqual(res.Balances.Len(), defaultLimit)
   134  
   135  	s.T().Log("verify page request with limit > defaultLimit, returns less or equal to `limit` records")
   136  	pageReq = &query.PageRequest{Limit: overLimit}
   137  	request = types.NewQueryAllBalancesRequest(addr1, pageReq, false)
   138  	res, err = queryClient.AllBalances(gocontext.Background(), request)
   139  	s.Require().NoError(err)
   140  	s.Require().Equal(res.Pagination.Total, uint64(0))
   141  	s.Require().NotNil(res.Pagination.NextKey)
   142  	s.Require().LessOrEqual(res.Balances.Len(), overLimit)
   143  
   144  	s.T().Log("verify paginate with custom limit and countTotal true")
   145  	pageReq = &query.PageRequest{Limit: underLimit, CountTotal: true}
   146  	request = types.NewQueryAllBalancesRequest(addr1, pageReq, false)
   147  	res, err = queryClient.AllBalances(gocontext.Background(), request)
   148  	s.Require().NoError(err)
   149  	s.Require().Equal(res.Balances.Len(), underLimit)
   150  	s.Require().NotNil(res.Pagination.NextKey)
   151  	s.Require().Equal(res.Pagination.Total, uint64(numBalances))
   152  
   153  	s.T().Log("verify paginate with custom limit and countTotal false")
   154  	pageReq = &query.PageRequest{Limit: defaultLimit, CountTotal: false}
   155  	request = types.NewQueryAllBalancesRequest(addr1, pageReq, false)
   156  	res, err = queryClient.AllBalances(gocontext.Background(), request)
   157  	s.Require().NoError(err)
   158  	s.Require().Equal(res.Balances.Len(), defaultLimit)
   159  	s.Require().NotNil(res.Pagination.NextKey)
   160  	s.Require().Equal(res.Pagination.Total, uint64(0))
   161  
   162  	s.T().Log("verify paginate with custom limit, key and countTotal false")
   163  	pageReq = &query.PageRequest{Key: res.Pagination.NextKey, Limit: defaultLimit, CountTotal: false}
   164  	request = types.NewQueryAllBalancesRequest(addr1, pageReq, false)
   165  	res, err = queryClient.AllBalances(gocontext.Background(), request)
   166  	s.Require().NoError(err)
   167  	s.Require().Equal(res.Balances.Len(), defaultLimit)
   168  	s.Require().NotNil(res.Pagination.NextKey)
   169  	s.Require().Equal(res.Pagination.Total, uint64(0))
   170  
   171  	s.T().Log("verify paginate for last page, results in records less than max limit")
   172  	pageReq = &query.PageRequest{Key: res.Pagination.NextKey, Limit: defaultLimit, CountTotal: false}
   173  	request = types.NewQueryAllBalancesRequest(addr1, pageReq, false)
   174  	res, err = queryClient.AllBalances(gocontext.Background(), request)
   175  	s.Require().NoError(err)
   176  	s.Require().LessOrEqual(res.Balances.Len(), defaultLimit)
   177  	s.Require().Equal(res.Balances.Len(), lastPageRecords)
   178  	s.Require().Nil(res.Pagination.NextKey)
   179  	s.Require().Equal(res.Pagination.Total, uint64(0))
   180  
   181  	s.T().Log("verify paginate with offset and limit")
   182  	pageReq = &query.PageRequest{Offset: 200, Limit: defaultLimit, CountTotal: false}
   183  	request = types.NewQueryAllBalancesRequest(addr1, pageReq, false)
   184  	res, err = queryClient.AllBalances(gocontext.Background(), request)
   185  	s.Require().NoError(err)
   186  	s.Require().LessOrEqual(res.Balances.Len(), defaultLimit)
   187  	s.Require().Equal(res.Balances.Len(), lastPageRecords)
   188  	s.Require().Nil(res.Pagination.NextKey)
   189  	s.Require().Equal(res.Pagination.Total, uint64(0))
   190  
   191  	s.T().Log("verify paginate with offset and limit")
   192  	pageReq = &query.PageRequest{Offset: 100, Limit: defaultLimit, CountTotal: false}
   193  	request = types.NewQueryAllBalancesRequest(addr1, pageReq, false)
   194  	res, err = queryClient.AllBalances(gocontext.Background(), request)
   195  	s.Require().NoError(err)
   196  	s.Require().LessOrEqual(res.Balances.Len(), defaultLimit)
   197  	s.Require().NotNil(res.Pagination.NextKey)
   198  	s.Require().Equal(res.Pagination.Total, uint64(0))
   199  
   200  	s.T().Log("verify paginate with offset and key - error")
   201  	pageReq = &query.PageRequest{Key: res.Pagination.NextKey, Offset: 100, Limit: defaultLimit, CountTotal: false}
   202  	request = types.NewQueryAllBalancesRequest(addr1, pageReq, false)
   203  	_, err = queryClient.AllBalances(gocontext.Background(), request)
   204  	s.Require().Error(err)
   205  	s.Require().Equal("rpc error: code = InvalidArgument desc = paginate: invalid request, either offset or key is expected, got both", err.Error())
   206  
   207  	s.T().Log("verify paginate with offset greater than total results")
   208  	pageReq = &query.PageRequest{Offset: 300, Limit: defaultLimit, CountTotal: false}
   209  	request = types.NewQueryAllBalancesRequest(addr1, pageReq, false)
   210  	res, err = queryClient.AllBalances(gocontext.Background(), request)
   211  	s.Require().NoError(err)
   212  	s.Require().LessOrEqual(res.Balances.Len(), 0)
   213  	s.Require().Nil(res.Pagination.NextKey)
   214  }
   215  
   216  func (s *paginationTestSuite) TestReversePagination() {
   217  	queryHelper := baseapp.NewQueryServerTestHelper(s.ctx, s.interfaceReg)
   218  	types.RegisterQueryServer(queryHelper, s.bankKeeper)
   219  	queryClient := types.NewQueryClient(queryHelper)
   220  
   221  	var balances sdk.Coins
   222  
   223  	for i := 0; i < numBalances; i++ {
   224  		denom := fmt.Sprintf("foo%ddenom", i)
   225  		balances = append(balances, sdk.NewInt64Coin(denom, 100))
   226  	}
   227  
   228  	balances = balances.Sort()
   229  	addr1 := sdk.AccAddress(secp256k1.GenPrivKey().PubKey().Address())
   230  	acc1 := s.accountKeeper.NewAccountWithAddress(s.ctx, addr1)
   231  	s.accountKeeper.SetAccount(s.ctx, acc1)
   232  	s.Require().NoError(testutil.FundAccount(s.ctx, s.bankKeeper, addr1, balances))
   233  
   234  	s.T().Log("verify paginate with custom limit and countTotal, Reverse false")
   235  	pageReq := &query.PageRequest{Limit: 2, CountTotal: true, Reverse: true, Key: nil}
   236  	request := types.NewQueryAllBalancesRequest(addr1, pageReq, false)
   237  	res1, err := queryClient.AllBalances(gocontext.Background(), request)
   238  	s.Require().NoError(err)
   239  	s.Require().Equal(2, res1.Balances.Len())
   240  	s.Require().NotNil(res1.Pagination.NextKey)
   241  
   242  	s.T().Log("verify paginate with custom limit and countTotal, Reverse false")
   243  	pageReq = &query.PageRequest{Limit: 150}
   244  	request = types.NewQueryAllBalancesRequest(addr1, pageReq, false)
   245  	res1, err = queryClient.AllBalances(gocontext.Background(), request)
   246  	s.Require().NoError(err)
   247  	s.Require().Equal(res1.Balances.Len(), 150)
   248  	s.Require().NotNil(res1.Pagination.NextKey)
   249  	s.Require().Equal(res1.Pagination.Total, uint64(0))
   250  
   251  	s.T().Log("verify paginate with custom limit, key and Reverse true")
   252  	pageReq = &query.PageRequest{Limit: defaultLimit, Reverse: true}
   253  	request = types.NewQueryAllBalancesRequest(addr1, pageReq, false)
   254  	res, err := queryClient.AllBalances(gocontext.Background(), request)
   255  	s.Require().NoError(err)
   256  	s.Require().Equal(res.Balances.Len(), defaultLimit)
   257  	s.Require().NotNil(res.Pagination.NextKey)
   258  	s.Require().Equal(res.Pagination.Total, uint64(0))
   259  
   260  	s.T().Log("verify paginate with custom limit, key and Reverse true")
   261  	pageReq = &query.PageRequest{Offset: 100, Limit: defaultLimit, Reverse: true}
   262  	request = types.NewQueryAllBalancesRequest(addr1, pageReq, false)
   263  	res, err = queryClient.AllBalances(gocontext.Background(), request)
   264  	s.Require().NoError(err)
   265  	s.Require().Equal(res.Balances.Len(), defaultLimit)
   266  	s.Require().NotNil(res.Pagination.NextKey)
   267  	s.Require().Equal(res.Pagination.Total, uint64(0))
   268  
   269  	s.T().Log("verify paginate for last page, Reverse true")
   270  	pageReq = &query.PageRequest{Offset: 200, Limit: defaultLimit, Reverse: true}
   271  	request = types.NewQueryAllBalancesRequest(addr1, pageReq, false)
   272  	res, err = queryClient.AllBalances(gocontext.Background(), request)
   273  	s.Require().NoError(err)
   274  	s.Require().Equal(res.Balances.Len(), lastPageRecords)
   275  	s.Require().Nil(res.Pagination.NextKey)
   276  	s.Require().Equal(res.Pagination.Total, uint64(0))
   277  
   278  	s.T().Log("verify page request with limit > defaultLimit, returns less or equal to `limit` records")
   279  	pageReq = &query.PageRequest{Limit: overLimit, Reverse: true}
   280  	request = types.NewQueryAllBalancesRequest(addr1, pageReq, false)
   281  	res, err = queryClient.AllBalances(gocontext.Background(), request)
   282  	s.Require().NoError(err)
   283  	s.Require().Equal(res.Pagination.Total, uint64(0))
   284  	s.Require().NotNil(res.Pagination.NextKey)
   285  	s.Require().LessOrEqual(res.Balances.Len(), overLimit)
   286  
   287  	s.T().Log("verify paginate with custom limit, key, countTotal false and Reverse true")
   288  	pageReq = &query.PageRequest{Key: res1.Pagination.NextKey, Limit: 50, Reverse: true}
   289  	request = types.NewQueryAllBalancesRequest(addr1, pageReq, false)
   290  	res, err = queryClient.AllBalances(gocontext.Background(), request)
   291  	s.Require().NoError(err)
   292  	s.Require().Equal(res.Balances.Len(), 50)
   293  	s.Require().NotNil(res.Pagination.NextKey)
   294  	s.Require().Equal(res.Pagination.Total, uint64(0))
   295  
   296  	s.T().Log("verify Reverse pagination returns valid result")
   297  	s.Require().Equal(balances[101:151].String(), res.Balances.Sort().String())
   298  
   299  	s.T().Log("verify paginate with custom limit, key, countTotal false and Reverse true")
   300  	pageReq = &query.PageRequest{Key: res.Pagination.NextKey, Limit: 50, Reverse: true}
   301  	request = types.NewQueryAllBalancesRequest(addr1, pageReq, false)
   302  	res, err = queryClient.AllBalances(gocontext.Background(), request)
   303  	s.Require().NoError(err)
   304  	s.Require().Equal(res.Balances.Len(), 50)
   305  	s.Require().NotNil(res.Pagination.NextKey)
   306  	s.Require().Equal(res.Pagination.Total, uint64(0))
   307  
   308  	s.T().Log("verify Reverse pagination returns valid result")
   309  	s.Require().Equal(balances[51:101].String(), res.Balances.Sort().String())
   310  
   311  	s.T().Log("verify paginate for last page Reverse true")
   312  	pageReq = &query.PageRequest{Key: res.Pagination.NextKey, Limit: defaultLimit, Reverse: true}
   313  	request = types.NewQueryAllBalancesRequest(addr1, pageReq, false)
   314  	res, err = queryClient.AllBalances(gocontext.Background(), request)
   315  	s.Require().NoError(err)
   316  	s.Require().Equal(res.Balances.Len(), 51)
   317  	s.Require().Nil(res.Pagination.NextKey)
   318  	s.Require().Equal(res.Pagination.Total, uint64(0))
   319  
   320  	s.T().Log("verify Reverse pagination returns valid result")
   321  	s.Require().Equal(balances[0:51].String(), res.Balances.Sort().String())
   322  
   323  	s.T().Log("verify paginate with offset and key - error")
   324  	pageReq = &query.PageRequest{Key: res1.Pagination.NextKey, Offset: 100, Limit: defaultLimit, CountTotal: false}
   325  	request = types.NewQueryAllBalancesRequest(addr1, pageReq, false)
   326  	_, err = queryClient.AllBalances(gocontext.Background(), request)
   327  	s.Require().Error(err)
   328  	s.Require().Equal("rpc error: code = InvalidArgument desc = paginate: invalid request, either offset or key is expected, got both", err.Error())
   329  
   330  	s.T().Log("verify paginate with offset greater than total results")
   331  	pageReq = &query.PageRequest{Offset: 300, Limit: defaultLimit, CountTotal: false, Reverse: true}
   332  	request = types.NewQueryAllBalancesRequest(addr1, pageReq, false)
   333  	res, err = queryClient.AllBalances(gocontext.Background(), request)
   334  	s.Require().NoError(err)
   335  	s.Require().LessOrEqual(res.Balances.Len(), 0)
   336  	s.Require().Nil(res.Pagination.NextKey)
   337  }
   338  
   339  func (s *paginationTestSuite) TestPaginate() {
   340  	var balances sdk.Coins
   341  
   342  	for i := 0; i < 2; i++ {
   343  		denom := fmt.Sprintf("foo%ddenom", i)
   344  		balances = append(balances, sdk.NewInt64Coin(denom, 100))
   345  	}
   346  
   347  	balances = balances.Sort()
   348  	addr1 := sdk.AccAddress([]byte("addr1"))
   349  	acc1 := s.accountKeeper.NewAccountWithAddress(s.ctx, addr1)
   350  	s.accountKeeper.SetAccount(s.ctx, acc1)
   351  	err := testutil.FundAccount(s.ctx, s.bankKeeper, addr1, balances)
   352  	if err != nil { // should return no error
   353  		fmt.Println(err)
   354  	}
   355  	// Paginate example
   356  	pageReq := &query.PageRequest{Key: nil, Limit: 1, CountTotal: true}
   357  	request := types.NewQueryAllBalancesRequest(addr1, pageReq, false)
   358  	balResult := sdk.NewCoins()
   359  	authStore := s.ctx.KVStore(s.app.UnsafeFindStoreKey(types.StoreKey))
   360  	balancesStore := prefix.NewStore(authStore, types.BalancesPrefix)
   361  	accountStore := prefix.NewStore(balancesStore, address.MustLengthPrefix(addr1))
   362  	pageRes, err := query.Paginate(accountStore, request.Pagination, func(key, value []byte) error {
   363  		var amount math.Int
   364  		err := amount.Unmarshal(value)
   365  		if err != nil {
   366  			return err
   367  		}
   368  		balResult = append(balResult, sdk.NewCoin(string(key), amount))
   369  		return nil
   370  	})
   371  	if err != nil { // should return no error
   372  		fmt.Println(err)
   373  	}
   374  	fmt.Println(&types.QueryAllBalancesResponse{Balances: balResult, Pagination: pageRes})
   375  	// Output:
   376  	// balances:<denom:"foo0denom" amount:"100" > pagination:<next_key:"foo1denom" total:2 >
   377  }