From 8748a3d473fa6a8c083b9051f1c764c213f31c28 Mon Sep 17 00:00:00 2001 From: Prathamesh Musale Date: Thu, 29 Feb 2024 10:09:23 +0530 Subject: [PATCH] Add registry module integration tests --- tests/integration/data/examples/example1.yml | 7 + .../data/examples/general_record_example.yml | 7 + .../examples/service_provider_example.yml | 13 + .../examples/website_registration_example.yml | 7 + .../registry/keeper/common_test.go | 26 ++ .../registry/keeper/query_server_test.go | 403 +++++++++++++++++- x/registry/keeper/keeper.go | 51 +++ 7 files changed, 510 insertions(+), 4 deletions(-) create mode 100644 tests/integration/data/examples/example1.yml create mode 100644 tests/integration/data/examples/general_record_example.yml create mode 100644 tests/integration/data/examples/service_provider_example.yml create mode 100644 tests/integration/data/examples/website_registration_example.yml diff --git a/tests/integration/data/examples/example1.yml b/tests/integration/data/examples/example1.yml new file mode 100644 index 00000000..3f7b4863 --- /dev/null +++ b/tests/integration/data/examples/example1.yml @@ -0,0 +1,7 @@ +record: + attr1: value1 + attr2: value2 + link1: + /: QmSnuWmxptJZdLJpKRarxBMS2Ju2oANVrgbr2xWbie9b2D + link2: + /: QmP8jTG1m9GSDJLCbeWhVSVgEzCPPwXRdCRuJtQ5Tz9Kc9 diff --git a/tests/integration/data/examples/general_record_example.yml b/tests/integration/data/examples/general_record_example.yml new file mode 100644 index 00000000..5b3ef3b5 --- /dev/null +++ b/tests/integration/data/examples/general_record_example.yml @@ -0,0 +1,7 @@ +record: + type: GeneralRecord + name: foo + version: 1.0.0 + tags: + - tagA + - tagB diff --git a/tests/integration/data/examples/service_provider_example.yml b/tests/integration/data/examples/service_provider_example.yml new file mode 100644 index 00000000..87ea35ca --- /dev/null +++ b/tests/integration/data/examples/service_provider_example.yml @@ -0,0 +1,13 @@ + +record: + type: ServiceProviderRegistration + bond_id: madeUpBondID + laconic_id: madeUpLaconicID + version: 1.0.0 + x500: + common_name: cerc-io + organization_unit: xyz + organization_name: abc + state_name: california + country: US + locality_name: local \ No newline at end of file diff --git a/tests/integration/data/examples/website_registration_example.yml b/tests/integration/data/examples/website_registration_example.yml new file mode 100644 index 00000000..419f62fd --- /dev/null +++ b/tests/integration/data/examples/website_registration_example.yml @@ -0,0 +1,7 @@ +record: + type: WebsiteRegistrationRecord + url: https://cerc.io + repo_registration_record_cid: QmSnuWmxptJZdLJpKRarxBMS2Ju2oANVrgbr2xWbie9b2D + build_artifact_cid: QmP8jTG1m9GSDJLCbeWhVSVgEzCPPwXRdCRuJtQ5Tz9Kc9 + tls_cerc_cid: QmbWqxBEKC3P8tqsKc98xmWNzrzDtRLMiMPL8wBuTGsMnR + version: 1.0.0 \ No newline at end of file diff --git a/tests/integration/registry/keeper/common_test.go b/tests/integration/registry/keeper/common_test.go index f6237172..05712894 100644 --- a/tests/integration/registry/keeper/common_test.go +++ b/tests/integration/registry/keeper/common_test.go @@ -3,10 +3,14 @@ package keeper_test import ( "testing" + "cosmossdk.io/math" + simtestutil "github.com/cosmos/cosmos-sdk/testutil/sims" + sdk "github.com/cosmos/cosmos-sdk/types" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/suite" integrationTest "git.vdb.to/cerc-io/laconic2d/tests/integration" + bondTypes "git.vdb.to/cerc-io/laconic2d/x/bond" types "git.vdb.to/cerc-io/laconic2d/x/registry" ) @@ -15,6 +19,9 @@ type KeeperTestSuite struct { integrationTest.TestFixture queryClient types.QueryClient + + accounts []sdk.AccAddress + bond bondTypes.Bond } func (kts *KeeperTestSuite) SetupTest() { @@ -26,8 +33,27 @@ func (kts *KeeperTestSuite) SetupTest() { qr := kts.App.QueryHelper() kts.queryClient = types.NewQueryClient(qr) + + // Create a bond + bond, err := kts.createBond() + assert.Nil(kts.T(), err) + kts.bond = *bond } func TestRegistryKeeperTestSuite(t *testing.T) { suite.Run(t, new(KeeperTestSuite)) } + +func (kts *KeeperTestSuite) createBond() (*bondTypes.Bond, error) { + ctx := kts.SdkCtx + + // Create a funded account + kts.accounts = simtestutil.AddTestAddrs(kts.BankKeeper, integrationTest.BondDenomProvider{}, ctx, 1, math.NewInt(100000000000)) + + bond, err := kts.BondKeeper.CreateBond(ctx, kts.accounts[0], sdk.NewCoins(sdk.NewCoin(sdk.DefaultBondDenom, math.NewInt(1000000000)))) + if err != nil { + return nil, err + } + + return bond, nil +} diff --git a/tests/integration/registry/keeper/query_server_test.go b/tests/integration/registry/keeper/query_server_test.go index ad0d50aa..542f4a5e 100644 --- a/tests/integration/registry/keeper/query_server_test.go +++ b/tests/integration/registry/keeper/query_server_test.go @@ -3,25 +3,420 @@ package keeper_test import ( "context" "fmt" + "path/filepath" + "reflect" - registrytypes "git.vdb.to/cerc-io/laconic2d/x/registry" + types "git.vdb.to/cerc-io/laconic2d/x/registry" + "git.vdb.to/cerc-io/laconic2d/x/registry/client/cli" + "git.vdb.to/cerc-io/laconic2d/x/registry/helpers" + registryKeeper "git.vdb.to/cerc-io/laconic2d/x/registry/keeper" ) func (kts *KeeperTestSuite) TestGrpcQueryParams() { testCases := []struct { msg string - req *registrytypes.QueryParamsRequest + req *types.QueryParamsRequest }{ { "Get Params", - ®istrytypes.QueryParamsRequest{}, + &types.QueryParamsRequest{}, }, } for _, test := range testCases { kts.Run(fmt.Sprintf("Case %s ", test.msg), func() { resp, _ := kts.queryClient.Params(context.Background(), test.req) - defaultParams := registrytypes.DefaultParams() + defaultParams := types.DefaultParams() kts.Require().Equal(defaultParams.String(), resp.GetParams().String()) }) } } + +func (kts *KeeperTestSuite) TestGrpcGetRecordLists() { + ctx, queryClient := kts.SdkCtx, kts.queryClient + sr := kts.Require() + + var recordId string + examples := []string{ + "../../data/examples/service_provider_example.yml", + "../../data/examples/website_registration_example.yml", + "../../data/examples/general_record_example.yml", + } + testCases := []struct { + msg string + req *types.QueryRecordsRequest + createRecords bool + expErr bool + noOfRecords int + }{ + { + "Empty Records", + &types.QueryRecordsRequest{}, + false, + false, + 0, + }, + { + "List Records", + &types.QueryRecordsRequest{}, + true, + false, + 3, + }, + // TODO: Uncomment after implementing filtering by attributes + // { + // "Filter with type", + // &types.QueryRecordsRequest{ + // Attributes: []*types.QueryRecordsRequest_KeyValueInput{ + // { + // Key: "type", + // Value: &types.QueryRecordsRequest_ValueInput{ + // Value: &types.QueryRecordsRequest_ValueInput_String_{String_: "WebsiteRegistrationRecord"}, + // }, + // }, + // }, + // All: true, + // }, + // true, + // false, + // 1, + // }, + // Skip the following test as querying with recursive values not supported (PR https://git.vdb.to/cerc-io/laconicd/pulls/112) + // See function RecordsFromAttributes (QueryValueToJSON call) in the registry keeper implementation (x/registry/keeper/keeper.go) + // { + // "Filter with tag (extant) (https://git.vdb.to/cerc-io/laconicd/issues/129)", + // &types.QueryRecordsRequest{ + // Attributes: []*types.QueryRecordsRequest_KeyValueInput{ + // { + // Key: "tags", + // // Value: &types.QueryRecordsRequest_ValueInput{ + // // Value: &types.QueryRecordsRequest_ValueInput_String_{"tagA"}, + // // }, + // Value: &types.QueryRecordsRequest_ValueInput{ + // Value: &types.QueryRecordsRequest_ValueInput_Array{Array: &types.QueryRecordsRequest_ArrayInput{ + // Values: []*types.QueryRecordsRequest_ValueInput{ + // { + // Value: &types.QueryRecordsRequest_ValueInput_String_{"tagA"}, + // }, + // }, + // }}, + // }, + // // Throws: "Recursive query values are not supported" + // }, + // }, + // All: true, + // }, + // true, + // false, + // 1, + // }, + // { + // "Filter with tag (non-existent) (https://git.vdb.to/cerc-io/laconicd/issues/129)", + // &types.QueryRecordsRequest{ + // Attributes: []*types.QueryRecordsRequest_KeyValueInput{ + // { + // Key: "tags", + // Value: &types.QueryRecordsRequest_ValueInput{ + // Value: &types.QueryRecordsRequest_ValueInput_String_{String_: "NOEXIST"}, + // }, + // }, + // }, + // All: true, + // }, + // true, + // false, + // 0, + // }, + // { + // "Filter test for key collision (https://git.vdb.to/cerc-io/laconicd/issues/122)", + // &types.QueryRecordsRequest{ + // Attributes: []*types.QueryRecordsRequest_KeyValueInput{ + // { + // Key: "typ", + // Value: &types.QueryRecordsRequest_ValueInput{ + // Value: &types.QueryRecordsRequest_ValueInput_String_{String_: "eWebsiteRegistrationRecord"}, + // }, + // }, + // }, + // All: true, + // }, + // true, + // false, + // 0, + // }, + // { + // "Filter with attributes ServiceProviderRegistration", + // &types.QueryRecordsRequest{ + // Attributes: []*types.QueryRecordsRequest_KeyValueInput{ + // { + // Key: "x500state_name", + // Value: &types.QueryRecordsRequest_ValueInput{ + // Value: &types.QueryRecordsRequest_ValueInput_String_{String_: "california"}, + // }, + // }, + // }, + // All: true, + // }, + // true, + // false, + // 1, + // }, + } + for _, test := range testCases { + kts.Run(fmt.Sprintf("Case %s ", test.msg), func() { + if test.createRecords { + for _, example := range examples { + filePath, err := filepath.Abs(example) + sr.NoError(err) + payloadType, err := cli.GetPayloadFromFile(filePath) + sr.NoError(err) + payload := payloadType.ToPayload() + record, err := kts.RegistryKeeper.SetRecord(ctx, types.MsgSetRecord{ + BondId: kts.bond.GetId(), + Signer: kts.accounts[0].String(), + Payload: payload, + }) + sr.NoError(err) + sr.NotNil(record.Id) + } + } + + resp, err := queryClient.Records(context.Background(), test.req) + + if test.expErr { + kts.Error(err) + } else { + sr.NoError(err) + sr.Equal(test.noOfRecords, len(resp.GetRecords())) + if test.createRecords && test.noOfRecords > 0 { + recordId = resp.GetRecords()[0].GetId() + sr.NotZero(resp.GetRecords()) + sr.Equal(resp.GetRecords()[0].GetBondId(), kts.bond.GetId()) + + for _, record := range resp.GetRecords() { + recAttr := helpers.MustUnmarshalJSON[types.AttributeMap](record.Attributes) + + for _, attr := range test.req.GetAttributes() { + enc, err := registryKeeper.QueryValueToJSON(attr.Value) + sr.NoError(err) + av := helpers.MustUnmarshalJSON[any](enc) + + if nil != av && nil != recAttr[attr.Key] && + reflect.Slice == reflect.TypeOf(recAttr[attr.Key]).Kind() && + reflect.Slice != reflect.TypeOf(av).Kind() { + found := false + allValues := recAttr[attr.Key].([]interface{}) + for i := range allValues { + if av == allValues[i] { + fmt.Printf("Found %s in %s", allValues[i], recAttr[attr.Key]) + found = true + } + } + sr.Equal(true, found, fmt.Sprintf("Unable to find %s in %s", av, recAttr[attr.Key])) + } else { + if attr.Key[:4] == "x500" { + sr.Equal(av, recAttr["x500"].(map[string]interface{})[attr.Key[4:]]) + } else { + sr.Equal(av, recAttr[attr.Key]) + } + } + } + } + } + } + }) + } + + // Get the records by record id + testCases1 := []struct { + msg string + req *types.QueryRecordByIdRequest + createRecord bool + expErr bool + noOfRecords int + }{ + { + "Invalid Request without record id", + &types.QueryRecordByIdRequest{}, + false, + true, + 0, + }, + { + "With Record ID", + &types.QueryRecordByIdRequest{ + Id: recordId, + }, + true, + false, + 1, + }, + } + for _, test := range testCases1 { + kts.Run(fmt.Sprintf("Case %s ", test.msg), func() { + resp, err := queryClient.GetRecord(context.Background(), test.req) + + if test.expErr { + kts.Error(err) + } else { + sr.NoError(err) + sr.NotNil(resp.GetRecord()) + if test.createRecord { + sr.Equal(resp.GetRecord().BondId, kts.bond.GetId()) + sr.Equal(resp.GetRecord().Id, recordId) + } + } + }) + } + + // Get the records by record id + testCasesByBondID := []struct { + msg string + req *types.QueryRecordsByBondIdRequest + createRecord bool + expErr bool + noOfRecords int + }{ + { + "Invalid Request without bond id", + &types.QueryRecordsByBondIdRequest{}, + false, + true, + 0, + }, + { + "With Bond ID", + &types.QueryRecordsByBondIdRequest{ + Id: kts.bond.GetId(), + }, + true, + false, + 1, + }, + } + for _, test := range testCasesByBondID { + kts.Run(fmt.Sprintf("Case %s ", test.msg), func() { + resp, err := queryClient.GetRecordsByBondId(context.Background(), test.req) + + if test.expErr { + sr.Zero(resp.GetRecords()) + } else { + sr.NoError(err) + sr.NotNil(resp.GetRecords()) + if test.createRecord { + sr.NotZero(resp.GetRecords()) + sr.Equal(resp.GetRecords()[0].GetBondId(), kts.bond.GetId()) + } + } + }) + } +} + +func (kts *KeeperTestSuite) TestGrpcQueryRegistryModuleBalance() { + queryClient, ctx := kts.queryClient, kts.SdkCtx + sr := kts.Require() + examples := []string{ + "../../data/examples/service_provider_example.yml", + "../../data/examples/website_registration_example.yml", + } + testCases := []struct { + msg string + req *types.QueryGetRegistryModuleBalanceRequest + createRecords bool + expErr bool + noOfRecords int + }{ + { + "Get Module Balance", + &types.QueryGetRegistryModuleBalanceRequest{}, + true, + false, + 1, + }, + } + for _, test := range testCases { + kts.Run(fmt.Sprintf("Case %s ", test.msg), func() { + if test.createRecords { + for _, example := range examples { + filePath, err := filepath.Abs(example) + sr.NoError(err) + payloadType, err := cli.GetPayloadFromFile(filePath) + sr.NoError(err) + payload := payloadType.ToPayload() + record, err := kts.RegistryKeeper.SetRecord(ctx, types.MsgSetRecord{ + BondId: kts.bond.GetId(), + Signer: kts.accounts[0].String(), + Payload: payload, + }) + sr.NoError(err) + sr.NotNil(record.Id) + } + } + resp, err := queryClient.GetRegistryModuleBalance(context.Background(), test.req) + if test.expErr { + kts.Error(err) + } else { + sr.NoError(err) + sr.Equal(test.noOfRecords, len(resp.GetBalances())) + if test.createRecords { + balance := resp.GetBalances()[0] + sr.Equal(balance.AccountName, types.RecordRentModuleAccountName) + } + } + }) + } +} + +func (kts *KeeperTestSuite) TestGrpcQueryWhoIs() { + queryClient, ctx := kts.queryClient, kts.SdkCtx + sr := kts.Require() + authorityName := "TestGrpcQueryWhoIs" + + testCases := []struct { + msg string + req *types.QueryWhoisRequest + createName bool + expErr bool + noOfRecords int + }{ + { + "Invalid Request without name", + &types.QueryWhoisRequest{}, + false, + true, + 1, + }, + // { + // "Success", + // &types.QueryWhoisRequest{}, + // true, + // false, + // 1, + // }, + } + for _, test := range testCases { + kts.Run(fmt.Sprintf("Case %s ", test.msg), func() { + if test.createName { + err := kts.RegistryKeeper.ReserveAuthority(ctx, types.MsgReserveAuthority{ + Name: authorityName, + Signer: kts.accounts[0].String(), + Owner: kts.accounts[0].String(), + }) + sr.NoError(err) + test.req = &types.QueryWhoisRequest{Name: authorityName} + } + resp, err := queryClient.Whois(context.Background(), test.req) + if test.expErr { + kts.Error(err) + sr.Nil(resp) + } else { + sr.NoError(err) + if test.createName { + nameAuth := resp.NameAuthority + sr.NotNil(nameAuth) + sr.Equal(nameAuth.OwnerAddress, kts.accounts[0].String()) + sr.Equal(types.AuthorityActive, nameAuth.Status) + } + } + }) + } +} diff --git a/x/registry/keeper/keeper.go b/x/registry/keeper/keeper.go index 2d9c2c32..320e3672 100644 --- a/x/registry/keeper/keeper.go +++ b/x/registry/keeper/keeper.go @@ -19,8 +19,10 @@ import ( auth "github.com/cosmos/cosmos-sdk/x/auth/keeper" bank "github.com/cosmos/cosmos-sdk/x/bank/keeper" "github.com/gibson042/canonicaljson-go" + cid "github.com/ipfs/go-cid" "github.com/ipld/go-ipld-prime" "github.com/ipld/go-ipld-prime/codec/dagjson" + cidlink "github.com/ipld/go-ipld-prime/linking/cid" "github.com/ipld/go-ipld-prime/node/basicnode" auctionkeeper "git.vdb.to/cerc-io/laconic2d/x/auction/keeper" @@ -240,6 +242,55 @@ func (k Keeper) RecordsFromAttributes(ctx sdk.Context, attributes []*registrytyp panic("unimplemented") } +// TODO not recursive, and only should be if we want to support querying with whole sub-objects, +// which seems unnecessary. +func QueryValueToJSON(input *registrytypes.QueryRecordsRequest_ValueInput) ([]byte, error) { + np := basicnode.Prototype.Any + nb := np.NewBuilder() + + switch value := input.GetValue().(type) { + case *registrytypes.QueryRecordsRequest_ValueInput_String_: + err := nb.AssignString(value.String_) + if err != nil { + return nil, err + } + case *registrytypes.QueryRecordsRequest_ValueInput_Int: + err := nb.AssignInt(value.Int) + if err != nil { + return nil, err + } + case *registrytypes.QueryRecordsRequest_ValueInput_Float: + err := nb.AssignFloat(value.Float) + if err != nil { + return nil, err + } + case *registrytypes.QueryRecordsRequest_ValueInput_Boolean: + err := nb.AssignBool(value.Boolean) + if err != nil { + return nil, err + } + case *registrytypes.QueryRecordsRequest_ValueInput_Link: + link := cidlink.Link{Cid: cid.MustParse(value.Link)} + err := nb.AssignLink(link) + if err != nil { + return nil, err + } + case *registrytypes.QueryRecordsRequest_ValueInput_Array: + return nil, fmt.Errorf("recursive query values are not supported") + case *registrytypes.QueryRecordsRequest_ValueInput_Map: + return nil, fmt.Errorf("recursive query values are not supported") + default: + return nil, fmt.Errorf("value has unexpected type %T", value) + } + + n := nb.Build() + var buf bytes.Buffer + if err := dagjson.Encode(n, &buf); err != nil { + return nil, fmt.Errorf("encoding value to JSON failed: %w", err) + } + return buf.Bytes(), nil +} + // PutRecord - saves a record to the store. func (k Keeper) SaveRecord(ctx sdk.Context, record registrytypes.Record) error { return k.Records.Set(ctx, record.Id, record)