From 5a667537eeb25790879966611ee985dcb73125c4 Mon Sep 17 00:00:00 2001 From: Daniel Olshansky Date: Tue, 10 Oct 2023 16:11:16 -0400 Subject: [PATCH 1/2] ignite scaffold map application --module application --signer address --no-message --index address --yes --- docs/static/openapi.yml | 210 ++++++++++++++++++ proto/pocket/application/application.proto | 10 + proto/pocket/application/genesis.proto | 6 +- proto/pocket/application/query.proto | 38 +++- x/application/client/cli/query.go | 2 + x/application/client/cli/query_application.go | 78 +++++++ .../client/cli/query_application_test.go | 160 +++++++++++++ x/application/genesis.go | 5 + x/application/genesis_test.go | 9 + x/application/keeper/application.go | 63 ++++++ x/application/keeper/application_test.go | 63 ++++++ x/application/keeper/query_application.go | 57 +++++ .../keeper/query_application_test.go | 127 +++++++++++ x/application/types/genesis.go | 13 +- x/application/types/genesis_test.go | 24 +- x/application/types/key_application.go | 23 ++ 16 files changed, 882 insertions(+), 6 deletions(-) create mode 100644 proto/pocket/application/application.proto create mode 100644 x/application/client/cli/query_application.go create mode 100644 x/application/client/cli/query_application_test.go create mode 100644 x/application/keeper/application.go create mode 100644 x/application/keeper/application_test.go create mode 100644 x/application/keeper/query_application.go create mode 100644 x/application/keeper/query_application_test.go create mode 100644 x/application/types/key_application.go diff --git a/docs/static/openapi.yml b/docs/static/openapi.yml index 2fc5eb192..f2b85f96d 100644 --- a/docs/static/openapi.yml +++ b/docs/static/openapi.yml @@ -46437,6 +46437,167 @@ paths: } tags: - Query + /pocket/application/application: + get: + operationId: PocketApplicationApplicationAll + responses: + '200': + description: A successful response. + schema: + type: object + properties: + application: + type: array + items: + type: object + properties: + address: + type: string + pagination: + type: object + properties: + next_key: + type: string + format: byte + description: |- + next_key is the key to be passed to PageRequest.key to + query the next page most efficiently. It will be empty if + there are no more results. + total: + type: string + format: uint64 + title: >- + total is total number of results available if + PageRequest.count_total + + was set, its value is undefined otherwise + description: >- + PageResponse is to be embedded in gRPC response messages where + the + + corresponding request message has used PageRequest. + + message SomeResponse { + repeated Bar results = 1; + PageResponse page = 2; + } + default: + description: An unexpected error response. + schema: + type: object + properties: + code: + type: integer + format: int32 + message: + type: string + details: + type: array + items: + type: object + properties: + '@type': + type: string + additionalProperties: {} + parameters: + - name: pagination.key + description: |- + key is a value returned in PageResponse.next_key to begin + querying the next page most efficiently. Only one of offset or key + should be set. + in: query + required: false + type: string + format: byte + - name: pagination.offset + description: >- + offset is a numeric offset that can be used when key is unavailable. + + It is less efficient than using key. Only one of offset or key + should + + be set. + in: query + required: false + type: string + format: uint64 + - name: pagination.limit + description: >- + limit is the total number of results to be returned in the result + page. + + If left empty it will default to a value to be set by each app. + in: query + required: false + type: string + format: uint64 + - name: pagination.count_total + description: >- + count_total is set to true to indicate that the result set should + include + + a count of the total number of items available for pagination in + UIs. + + count_total is only respected when offset is used. It is ignored + when key + + is set. + in: query + required: false + type: boolean + - name: pagination.reverse + description: >- + reverse is set to true if results are to be returned in the + descending order. + + + Since: cosmos-sdk 0.43 + in: query + required: false + type: boolean + tags: + - Query + /pocket/application/application/{address}: + get: + summary: Queries a list of Application items. + operationId: PocketApplicationApplication + responses: + '200': + description: A successful response. + schema: + type: object + properties: + application: + type: object + properties: + address: + type: string + default: + description: An unexpected error response. + schema: + type: object + properties: + code: + type: integer + format: int32 + message: + type: string + details: + type: array + items: + type: object + properties: + '@type': + type: string + additionalProperties: {} + parameters: + - name: address + in: path + required: true + type: string + tags: + - Query /pocket/application/params: get: summary: Parameters queries the parameters of the module. @@ -75320,9 +75481,58 @@ definitions: description: |- Version defines the versioning scheme used to negotiate the IBC verison in the connection handshake. + pocket.application.Application: + type: object + properties: + address: + type: string pocket.application.Params: type: object description: Params defines the parameters for the module. + pocket.application.QueryAllApplicationResponse: + type: object + properties: + application: + type: array + items: + type: object + properties: + address: + type: string + pagination: + type: object + properties: + next_key: + type: string + format: byte + description: |- + next_key is the key to be passed to PageRequest.key to + query the next page most efficiently. It will be empty if + there are no more results. + total: + type: string + format: uint64 + title: >- + total is total number of results available if + PageRequest.count_total + + was set, its value is undefined otherwise + description: |- + PageResponse is to be embedded in gRPC response messages where the + corresponding request message has used PageRequest. + + message SomeResponse { + repeated Bar results = 1; + PageResponse page = 2; + } + pocket.application.QueryGetApplicationResponse: + type: object + properties: + application: + type: object + properties: + address: + type: string pocket.application.QueryParamsResponse: type: object properties: diff --git a/proto/pocket/application/application.proto b/proto/pocket/application/application.proto new file mode 100644 index 000000000..784a839ab --- /dev/null +++ b/proto/pocket/application/application.proto @@ -0,0 +1,10 @@ +syntax = "proto3"; +package pocket.application; + +option go_package = "pocket/x/application/types"; + +message Application { + string address = 1; + +} + diff --git a/proto/pocket/application/genesis.proto b/proto/pocket/application/genesis.proto index 9a3d0c7fd..6598d6636 100644 --- a/proto/pocket/application/genesis.proto +++ b/proto/pocket/application/genesis.proto @@ -1,12 +1,16 @@ syntax = "proto3"; + package pocket.application; import "gogoproto/gogo.proto"; import "pocket/application/params.proto"; +import "pocket/application/application.proto"; option go_package = "pocket/x/application/types"; // GenesisState defines the application module's genesis state. message GenesisState { - Params params = 1 [(gogoproto.nullable) = false]; + Params params = 1 [(gogoproto.nullable) = false]; + repeated Application applicationList = 2 [(gogoproto.nullable) = false]; } + diff --git a/proto/pocket/application/query.proto b/proto/pocket/application/query.proto index 703bae698..fd25f232d 100644 --- a/proto/pocket/application/query.proto +++ b/proto/pocket/application/query.proto @@ -1,26 +1,58 @@ syntax = "proto3"; + package pocket.application; import "gogoproto/gogo.proto"; import "google/api/annotations.proto"; import "cosmos/base/query/v1beta1/pagination.proto"; import "pocket/application/params.proto"; +import "pocket/application/application.proto"; option go_package = "pocket/x/application/types"; // Query defines the gRPC querier service. service Query { + // Parameters queries the parameters of the module. - rpc Params(QueryParamsRequest) returns (QueryParamsResponse) { + rpc Params (QueryParamsRequest) returns (QueryParamsResponse) { option (google.api.http).get = "/pocket/application/params"; + + } + + // Queries a list of Application items. + rpc Application (QueryGetApplicationRequest) returns (QueryGetApplicationResponse) { + option (google.api.http).get = "/pocket/application/application/{address}"; + + } + rpc ApplicationAll (QueryAllApplicationRequest) returns (QueryAllApplicationResponse) { + option (google.api.http).get = "/pocket/application/application"; + } } - // QueryParamsRequest is request type for the Query/Params RPC method. message QueryParamsRequest {} // QueryParamsResponse is response type for the Query/Params RPC method. message QueryParamsResponse { + // params holds all the parameters of this module. Params params = 1 [(gogoproto.nullable) = false]; -} \ No newline at end of file +} + +message QueryGetApplicationRequest { + string address = 1; +} + +message QueryGetApplicationResponse { + Application application = 1 [(gogoproto.nullable) = false]; +} + +message QueryAllApplicationRequest { + cosmos.base.query.v1beta1.PageRequest pagination = 1; +} + +message QueryAllApplicationResponse { + repeated Application application = 1 [(gogoproto.nullable) = false]; + cosmos.base.query.v1beta1.PageResponse pagination = 2; +} + diff --git a/x/application/client/cli/query.go b/x/application/client/cli/query.go index 96e9379b9..2c42f0de6 100644 --- a/x/application/client/cli/query.go +++ b/x/application/client/cli/query.go @@ -25,6 +25,8 @@ func GetQueryCmd(queryRoute string) *cobra.Command { } cmd.AddCommand(CmdQueryParams()) + cmd.AddCommand(CmdListApplication()) + cmd.AddCommand(CmdShowApplication()) // this line is used by starport scaffolding # 1 return cmd diff --git a/x/application/client/cli/query_application.go b/x/application/client/cli/query_application.go new file mode 100644 index 000000000..a11a3059e --- /dev/null +++ b/x/application/client/cli/query_application.go @@ -0,0 +1,78 @@ +package cli + +import ( + "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/client/flags" + "github.com/spf13/cobra" + + "pocket/x/application/types" +) + +func CmdListApplication() *cobra.Command { + cmd := &cobra.Command{ + Use: "list-application", + Short: "list all application", + RunE: func(cmd *cobra.Command, args []string) error { + clientCtx, err := client.GetClientQueryContext(cmd) + if err != nil { + return err + } + + pageReq, err := client.ReadPageRequest(cmd.Flags()) + if err != nil { + return err + } + + queryClient := types.NewQueryClient(clientCtx) + + params := &types.QueryAllApplicationRequest{ + Pagination: pageReq, + } + + res, err := queryClient.ApplicationAll(cmd.Context(), params) + if err != nil { + return err + } + + return clientCtx.PrintProto(res) + }, + } + + flags.AddPaginationFlagsToCmd(cmd, cmd.Use) + flags.AddQueryFlagsToCmd(cmd) + + return cmd +} + +func CmdShowApplication() *cobra.Command { + cmd := &cobra.Command{ + Use: "show-application [address]", + Short: "shows a application", + Args: cobra.ExactArgs(1), + RunE: func(cmd *cobra.Command, args []string) (err error) { + clientCtx, err := client.GetClientQueryContext(cmd) + if err != nil { + return err + } + + queryClient := types.NewQueryClient(clientCtx) + + argAddress := args[0] + + params := &types.QueryGetApplicationRequest{ + Address: argAddress, + } + + res, err := queryClient.Application(cmd.Context(), params) + if err != nil { + return err + } + + return clientCtx.PrintProto(res) + }, + } + + flags.AddQueryFlagsToCmd(cmd) + + return cmd +} diff --git a/x/application/client/cli/query_application_test.go b/x/application/client/cli/query_application_test.go new file mode 100644 index 000000000..b423b5ad5 --- /dev/null +++ b/x/application/client/cli/query_application_test.go @@ -0,0 +1,160 @@ +package cli_test + +import ( + "fmt" + "strconv" + "testing" + + tmcli "github.com/cometbft/cometbft/libs/cli" + "github.com/cosmos/cosmos-sdk/client/flags" + clitestutil "github.com/cosmos/cosmos-sdk/testutil/cli" + "github.com/stretchr/testify/require" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" + + "pocket/testutil/network" + "pocket/testutil/nullify" + "pocket/x/application/client/cli" + "pocket/x/application/types" +) + +// Prevent strconv unused error +var _ = strconv.IntSize + +func networkWithApplicationObjects(t *testing.T, n int) (*network.Network, []types.Application) { + t.Helper() + cfg := network.DefaultConfig() + state := types.GenesisState{} + for i := 0; i < n; i++ { + application := types.Application{ + Address: strconv.Itoa(i), + } + nullify.Fill(&application) + state.ApplicationList = append(state.ApplicationList, application) + } + buf, err := cfg.Codec.MarshalJSON(&state) + require.NoError(t, err) + cfg.GenesisState[types.ModuleName] = buf + return network.New(t, cfg), state.ApplicationList +} + +func TestShowApplication(t *testing.T) { + net, objs := networkWithApplicationObjects(t, 2) + + ctx := net.Validators[0].ClientCtx + common := []string{ + fmt.Sprintf("--%s=json", tmcli.OutputFlag), + } + tests := []struct { + desc string + idAddress string + + args []string + err error + obj types.Application + }{ + { + desc: "found", + idAddress: objs[0].Address, + + args: common, + obj: objs[0], + }, + { + desc: "not found", + idAddress: strconv.Itoa(100000), + + args: common, + err: status.Error(codes.NotFound, "not found"), + }, + } + for _, tc := range tests { + t.Run(tc.desc, func(t *testing.T) { + args := []string{ + tc.idAddress, + } + args = append(args, tc.args...) + out, err := clitestutil.ExecTestCLICmd(ctx, cli.CmdShowApplication(), args) + if tc.err != nil { + stat, ok := status.FromError(tc.err) + require.True(t, ok) + require.ErrorIs(t, stat.Err(), tc.err) + } else { + require.NoError(t, err) + var resp types.QueryGetApplicationResponse + require.NoError(t, net.Config.Codec.UnmarshalJSON(out.Bytes(), &resp)) + require.NotNil(t, resp.Application) + require.Equal(t, + nullify.Fill(&tc.obj), + nullify.Fill(&resp.Application), + ) + } + }) + } +} + +func TestListApplication(t *testing.T) { + net, objs := networkWithApplicationObjects(t, 5) + + ctx := net.Validators[0].ClientCtx + request := func(next []byte, offset, limit uint64, total bool) []string { + args := []string{ + fmt.Sprintf("--%s=json", tmcli.OutputFlag), + } + if next == nil { + args = append(args, fmt.Sprintf("--%s=%d", flags.FlagOffset, offset)) + } else { + args = append(args, fmt.Sprintf("--%s=%s", flags.FlagPageKey, next)) + } + args = append(args, fmt.Sprintf("--%s=%d", flags.FlagLimit, limit)) + if total { + args = append(args, fmt.Sprintf("--%s", flags.FlagCountTotal)) + } + return args + } + t.Run("ByOffset", func(t *testing.T) { + step := 2 + for i := 0; i < len(objs); i += step { + args := request(nil, uint64(i), uint64(step), false) + out, err := clitestutil.ExecTestCLICmd(ctx, cli.CmdListApplication(), args) + require.NoError(t, err) + var resp types.QueryAllApplicationResponse + require.NoError(t, net.Config.Codec.UnmarshalJSON(out.Bytes(), &resp)) + require.LessOrEqual(t, len(resp.Application), step) + require.Subset(t, + nullify.Fill(objs), + nullify.Fill(resp.Application), + ) + } + }) + t.Run("ByKey", func(t *testing.T) { + step := 2 + var next []byte + for i := 0; i < len(objs); i += step { + args := request(next, 0, uint64(step), false) + out, err := clitestutil.ExecTestCLICmd(ctx, cli.CmdListApplication(), args) + require.NoError(t, err) + var resp types.QueryAllApplicationResponse + require.NoError(t, net.Config.Codec.UnmarshalJSON(out.Bytes(), &resp)) + require.LessOrEqual(t, len(resp.Application), step) + require.Subset(t, + nullify.Fill(objs), + nullify.Fill(resp.Application), + ) + next = resp.Pagination.NextKey + } + }) + t.Run("Total", func(t *testing.T) { + args := request(nil, 0, uint64(len(objs)), true) + out, err := clitestutil.ExecTestCLICmd(ctx, cli.CmdListApplication(), args) + require.NoError(t, err) + var resp types.QueryAllApplicationResponse + require.NoError(t, net.Config.Codec.UnmarshalJSON(out.Bytes(), &resp)) + require.NoError(t, err) + require.Equal(t, len(objs), int(resp.Pagination.Total)) + require.ElementsMatch(t, + nullify.Fill(objs), + nullify.Fill(resp.Application), + ) + }) +} diff --git a/x/application/genesis.go b/x/application/genesis.go index 2fafc9b5c..be808c6d9 100644 --- a/x/application/genesis.go +++ b/x/application/genesis.go @@ -8,6 +8,10 @@ import ( // InitGenesis initializes the module's state from a provided genesis state. func InitGenesis(ctx sdk.Context, k keeper.Keeper, genState types.GenesisState) { + // Set all the application + for _, elem := range genState.ApplicationList { + k.SetApplication(ctx, elem) + } // this line is used by starport scaffolding # genesis/module/init k.SetParams(ctx, genState.Params) } @@ -17,6 +21,7 @@ func ExportGenesis(ctx sdk.Context, k keeper.Keeper) *types.GenesisState { genesis := types.DefaultGenesis() genesis.Params = k.GetParams(ctx) + genesis.ApplicationList = k.GetAllApplication(ctx) // this line is used by starport scaffolding # genesis/module/export return genesis diff --git a/x/application/genesis_test.go b/x/application/genesis_test.go index 4a7411fc1..d3f7b4d44 100644 --- a/x/application/genesis_test.go +++ b/x/application/genesis_test.go @@ -14,6 +14,14 @@ func TestGenesis(t *testing.T) { genesisState := types.GenesisState{ Params: types.DefaultParams(), + ApplicationList: []types.Application{ + { + Address: "0", + }, + { + Address: "1", + }, + }, // this line is used by starport scaffolding # genesis/test/state } @@ -25,5 +33,6 @@ func TestGenesis(t *testing.T) { nullify.Fill(&genesisState) nullify.Fill(got) + require.ElementsMatch(t, genesisState.ApplicationList, got.ApplicationList) // this line is used by starport scaffolding # genesis/test/assert } diff --git a/x/application/keeper/application.go b/x/application/keeper/application.go new file mode 100644 index 000000000..c09bf2070 --- /dev/null +++ b/x/application/keeper/application.go @@ -0,0 +1,63 @@ +package keeper + +import ( + "github.com/cosmos/cosmos-sdk/store/prefix" + sdk "github.com/cosmos/cosmos-sdk/types" + "pocket/x/application/types" +) + +// SetApplication set a specific application in the store from its index +func (k Keeper) SetApplication(ctx sdk.Context, application types.Application) { + store := prefix.NewStore(ctx.KVStore(k.storeKey), types.KeyPrefix(types.ApplicationKeyPrefix)) + b := k.cdc.MustMarshal(&application) + store.Set(types.ApplicationKey( + application.Address, + ), b) +} + +// GetApplication returns a application from its index +func (k Keeper) GetApplication( + ctx sdk.Context, + address string, + +) (val types.Application, found bool) { + store := prefix.NewStore(ctx.KVStore(k.storeKey), types.KeyPrefix(types.ApplicationKeyPrefix)) + + b := store.Get(types.ApplicationKey( + address, + )) + if b == nil { + return val, false + } + + k.cdc.MustUnmarshal(b, &val) + return val, true +} + +// RemoveApplication removes a application from the store +func (k Keeper) RemoveApplication( + ctx sdk.Context, + address string, + +) { + store := prefix.NewStore(ctx.KVStore(k.storeKey), types.KeyPrefix(types.ApplicationKeyPrefix)) + store.Delete(types.ApplicationKey( + address, + )) +} + +// GetAllApplication returns all application +func (k Keeper) GetAllApplication(ctx sdk.Context) (list []types.Application) { + store := prefix.NewStore(ctx.KVStore(k.storeKey), types.KeyPrefix(types.ApplicationKeyPrefix)) + iterator := sdk.KVStorePrefixIterator(store, []byte{}) + + defer iterator.Close() + + for ; iterator.Valid(); iterator.Next() { + var val types.Application + k.cdc.MustUnmarshal(iterator.Value(), &val) + list = append(list, val) + } + + return +} diff --git a/x/application/keeper/application_test.go b/x/application/keeper/application_test.go new file mode 100644 index 000000000..0f65ea6f5 --- /dev/null +++ b/x/application/keeper/application_test.go @@ -0,0 +1,63 @@ +package keeper_test + +import ( + "strconv" + "testing" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/stretchr/testify/require" + keepertest "pocket/testutil/keeper" + "pocket/testutil/nullify" + "pocket/x/application/keeper" + "pocket/x/application/types" +) + +// Prevent strconv unused error +var _ = strconv.IntSize + +func createNApplication(keeper *keeper.Keeper, ctx sdk.Context, n int) []types.Application { + items := make([]types.Application, n) + for i := range items { + items[i].Address = strconv.Itoa(i) + + keeper.SetApplication(ctx, items[i]) + } + return items +} + +func TestApplicationGet(t *testing.T) { + keeper, ctx := keepertest.ApplicationKeeper(t) + items := createNApplication(keeper, ctx, 10) + for _, item := range items { + rst, found := keeper.GetApplication(ctx, + item.Address, + ) + require.True(t, found) + require.Equal(t, + nullify.Fill(&item), + nullify.Fill(&rst), + ) + } +} +func TestApplicationRemove(t *testing.T) { + keeper, ctx := keepertest.ApplicationKeeper(t) + items := createNApplication(keeper, ctx, 10) + for _, item := range items { + keeper.RemoveApplication(ctx, + item.Address, + ) + _, found := keeper.GetApplication(ctx, + item.Address, + ) + require.False(t, found) + } +} + +func TestApplicationGetAll(t *testing.T) { + keeper, ctx := keepertest.ApplicationKeeper(t) + items := createNApplication(keeper, ctx, 10) + require.ElementsMatch(t, + nullify.Fill(items), + nullify.Fill(keeper.GetAllApplication(ctx)), + ) +} diff --git a/x/application/keeper/query_application.go b/x/application/keeper/query_application.go new file mode 100644 index 000000000..a8a139b57 --- /dev/null +++ b/x/application/keeper/query_application.go @@ -0,0 +1,57 @@ +package keeper + +import ( + "context" + + "github.com/cosmos/cosmos-sdk/store/prefix" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/query" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" + "pocket/x/application/types" +) + +func (k Keeper) ApplicationAll(goCtx context.Context, req *types.QueryAllApplicationRequest) (*types.QueryAllApplicationResponse, error) { + if req == nil { + return nil, status.Error(codes.InvalidArgument, "invalid request") + } + + var applications []types.Application + ctx := sdk.UnwrapSDKContext(goCtx) + + store := ctx.KVStore(k.storeKey) + applicationStore := prefix.NewStore(store, types.KeyPrefix(types.ApplicationKeyPrefix)) + + pageRes, err := query.Paginate(applicationStore, req.Pagination, func(key []byte, value []byte) error { + var application types.Application + if err := k.cdc.Unmarshal(value, &application); err != nil { + return err + } + + applications = append(applications, application) + return nil + }) + + if err != nil { + return nil, status.Error(codes.Internal, err.Error()) + } + + return &types.QueryAllApplicationResponse{Application: applications, Pagination: pageRes}, nil +} + +func (k Keeper) Application(goCtx context.Context, req *types.QueryGetApplicationRequest) (*types.QueryGetApplicationResponse, error) { + if req == nil { + return nil, status.Error(codes.InvalidArgument, "invalid request") + } + ctx := sdk.UnwrapSDKContext(goCtx) + + val, found := k.GetApplication( + ctx, + req.Address, + ) + if !found { + return nil, status.Error(codes.NotFound, "not found") + } + + return &types.QueryGetApplicationResponse{Application: val}, nil +} diff --git a/x/application/keeper/query_application_test.go b/x/application/keeper/query_application_test.go new file mode 100644 index 000000000..714191885 --- /dev/null +++ b/x/application/keeper/query_application_test.go @@ -0,0 +1,127 @@ +package keeper_test + +import ( + "strconv" + "testing" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/query" + "github.com/stretchr/testify/require" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" + + keepertest "pocket/testutil/keeper" + "pocket/testutil/nullify" + "pocket/x/application/types" +) + +// Prevent strconv unused error +var _ = strconv.IntSize + +func TestApplicationQuerySingle(t *testing.T) { + keeper, ctx := keepertest.ApplicationKeeper(t) + wctx := sdk.WrapSDKContext(ctx) + msgs := createNApplication(keeper, ctx, 2) + tests := []struct { + desc string + request *types.QueryGetApplicationRequest + response *types.QueryGetApplicationResponse + err error + }{ + { + desc: "First", + request: &types.QueryGetApplicationRequest{ + Address: msgs[0].Address, + }, + response: &types.QueryGetApplicationResponse{Application: msgs[0]}, + }, + { + desc: "Second", + request: &types.QueryGetApplicationRequest{ + Address: msgs[1].Address, + }, + response: &types.QueryGetApplicationResponse{Application: msgs[1]}, + }, + { + desc: "KeyNotFound", + request: &types.QueryGetApplicationRequest{ + Address: strconv.Itoa(100000), + }, + err: status.Error(codes.NotFound, "not found"), + }, + { + desc: "InvalidRequest", + err: status.Error(codes.InvalidArgument, "invalid request"), + }, + } + for _, tc := range tests { + t.Run(tc.desc, func(t *testing.T) { + response, err := keeper.Application(wctx, tc.request) + if tc.err != nil { + require.ErrorIs(t, err, tc.err) + } else { + require.NoError(t, err) + require.Equal(t, + nullify.Fill(tc.response), + nullify.Fill(response), + ) + } + }) + } +} + +func TestApplicationQueryPaginated(t *testing.T) { + keeper, ctx := keepertest.ApplicationKeeper(t) + wctx := sdk.WrapSDKContext(ctx) + msgs := createNApplication(keeper, ctx, 5) + + request := func(next []byte, offset, limit uint64, total bool) *types.QueryAllApplicationRequest { + return &types.QueryAllApplicationRequest{ + Pagination: &query.PageRequest{ + Key: next, + Offset: offset, + Limit: limit, + CountTotal: total, + }, + } + } + t.Run("ByOffset", func(t *testing.T) { + step := 2 + for i := 0; i < len(msgs); i += step { + resp, err := keeper.ApplicationAll(wctx, request(nil, uint64(i), uint64(step), false)) + require.NoError(t, err) + require.LessOrEqual(t, len(resp.Application), step) + require.Subset(t, + nullify.Fill(msgs), + nullify.Fill(resp.Application), + ) + } + }) + t.Run("ByKey", func(t *testing.T) { + step := 2 + var next []byte + for i := 0; i < len(msgs); i += step { + resp, err := keeper.ApplicationAll(wctx, request(next, 0, uint64(step), false)) + require.NoError(t, err) + require.LessOrEqual(t, len(resp.Application), step) + require.Subset(t, + nullify.Fill(msgs), + nullify.Fill(resp.Application), + ) + next = resp.Pagination.NextKey + } + }) + t.Run("Total", func(t *testing.T) { + resp, err := keeper.ApplicationAll(wctx, request(nil, 0, 0, true)) + require.NoError(t, err) + require.Equal(t, len(msgs), int(resp.Pagination.Total)) + require.ElementsMatch(t, + nullify.Fill(msgs), + nullify.Fill(resp.Application), + ) + }) + t.Run("InvalidRequest", func(t *testing.T) { + _, err := keeper.ApplicationAll(wctx, nil) + require.ErrorIs(t, err, status.Error(codes.InvalidArgument, "invalid request")) + }) +} diff --git a/x/application/types/genesis.go b/x/application/types/genesis.go index 0af9b4416..fdd19d5ff 100644 --- a/x/application/types/genesis.go +++ b/x/application/types/genesis.go @@ -1,7 +1,7 @@ package types import ( -// this line is used by starport scaffolding # genesis/types/import + "fmt" ) // DefaultIndex is the default global index @@ -10,6 +10,7 @@ const DefaultIndex uint64 = 1 // DefaultGenesis returns the default genesis state func DefaultGenesis() *GenesisState { return &GenesisState{ + ApplicationList: []Application{}, // this line is used by starport scaffolding # genesis/types/default Params: DefaultParams(), } @@ -18,6 +19,16 @@ func DefaultGenesis() *GenesisState { // Validate performs basic genesis state validation returning an error upon any // failure. func (gs GenesisState) Validate() error { + // Check for duplicated index in application + applicationIndexMap := make(map[string]struct{}) + + for _, elem := range gs.ApplicationList { + index := string(ApplicationKey(elem.Address)) + if _, ok := applicationIndexMap[index]; ok { + return fmt.Errorf("duplicated index for application") + } + applicationIndexMap[index] = struct{}{} + } // this line is used by starport scaffolding # genesis/types/validate return gs.Params.Validate() diff --git a/x/application/types/genesis_test.go b/x/application/types/genesis_test.go index edff71cae..bf6c8f226 100644 --- a/x/application/types/genesis_test.go +++ b/x/application/types/genesis_test.go @@ -19,13 +19,35 @@ func TestGenesisState_Validate(t *testing.T) { valid: true, }, { - desc: "valid genesis state", + desc: "valid genesis state", genState: &types.GenesisState{ + ApplicationList: []types.Application{ + { + Address: "0", + }, + { + Address: "1", + }, + }, // this line is used by starport scaffolding # types/genesis/validField }, valid: true, }, + { + desc: "duplicated application", + genState: &types.GenesisState{ + ApplicationList: []types.Application{ + { + Address: "0", + }, + { + Address: "0", + }, + }, + }, + valid: false, + }, // this line is used by starport scaffolding # types/genesis/testcase } for _, tc := range tests { diff --git a/x/application/types/key_application.go b/x/application/types/key_application.go new file mode 100644 index 000000000..02cbefc4e --- /dev/null +++ b/x/application/types/key_application.go @@ -0,0 +1,23 @@ +package types + +import "encoding/binary" + +var _ binary.ByteOrder + +const ( + // ApplicationKeyPrefix is the prefix to retrieve all Application + ApplicationKeyPrefix = "Application/value/" +) + +// ApplicationKey returns the store key to retrieve a Application from the index fields +func ApplicationKey( + address string, +) []byte { + var key []byte + + addressBytes := []byte(address) + key = append(key, addressBytes...) + key = append(key, []byte("/")...) + + return key +} From 9591ef0b7e948dce5c27e1beb598141ef615e99a Mon Sep 17 00:00:00 2001 From: Daniel Olshansky Date: Tue, 10 Oct 2023 17:04:11 -0400 Subject: [PATCH 2/2] Update go.mod --- go.mod | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/go.mod b/go.mod index adc82ce90..5d310b203 100644 --- a/go.mod +++ b/go.mod @@ -17,6 +17,7 @@ require ( github.com/spf13/cobra v1.7.0 github.com/spf13/pflag v1.0.5 github.com/stretchr/testify v1.8.4 + google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 google.golang.org/grpc v1.56.1 gopkg.in/yaml.v2 v2.4.0 ) @@ -258,7 +259,6 @@ require ( gonum.org/v1/gonum v0.11.0 // indirect google.golang.org/api v0.122.0 // indirect google.golang.org/appengine v1.6.7 // indirect - google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 // indirect google.golang.org/protobuf v1.31.0 // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect