cosmossdk.io/client/v2@v2.0.0-beta.1/autocli/query_test.go (about)

     1  package autocli
     2  
     3  import (
     4  	"fmt"
     5  	"os"
     6  	"strings"
     7  	"testing"
     8  	"time"
     9  
    10  	"github.com/spf13/cobra"
    11  	"google.golang.org/protobuf/testing/protocmp"
    12  	"google.golang.org/protobuf/types/known/durationpb"
    13  	"google.golang.org/protobuf/types/known/timestamppb"
    14  	"gotest.tools/v3/assert"
    15  	"gotest.tools/v3/golden"
    16  
    17  	autocliv1 "cosmossdk.io/api/cosmos/autocli/v1"
    18  	queryv1beta1 "cosmossdk.io/api/cosmos/base/query/v1beta1"
    19  	basev1beta1 "cosmossdk.io/api/cosmos/base/v1beta1"
    20  	"cosmossdk.io/client/v2/internal/testpb"
    21  )
    22  
    23  var buildModuleQueryCommand = func(moduleName string, b *Builder) (*cobra.Command, error) {
    24  	cmd := topLevelCmd(moduleName, fmt.Sprintf("Querying commands for the %s module", moduleName))
    25  
    26  	err := b.AddQueryServiceCommands(cmd, testCmdDesc)
    27  	return cmd, err
    28  }
    29  
    30  var buildModuleQueryCommandOptional = func(moduleName string, b *Builder) (*cobra.Command, error) {
    31  	cmd := topLevelCmd(moduleName, fmt.Sprintf("Querying commands for the %s module", moduleName))
    32  
    33  	err := b.AddQueryServiceCommands(cmd, testCmdDescOptional)
    34  	return cmd, err
    35  }
    36  
    37  var buildModuleVargasOptional = func(moduleName string, b *Builder) (*cobra.Command, error) {
    38  	cmd := topLevelCmd(moduleName, fmt.Sprintf("Querying commands for the %s module", moduleName))
    39  
    40  	err := b.AddQueryServiceCommands(cmd, testCmdDescInvalidOptAndVargas)
    41  	return cmd, err
    42  }
    43  
    44  var testCmdDesc = &autocliv1.ServiceCommandDescriptor{
    45  	Service: testpb.Query_ServiceDesc.ServiceName,
    46  	RpcCommandOptions: []*autocliv1.RpcCommandOptions{
    47  		{
    48  			RpcMethod:  "Echo",
    49  			Use:        "echo [pos1] [pos2] [pos3...]",
    50  			Version:    "1.0",
    51  			Alias:      []string{"e"},
    52  			SuggestFor: []string{"eco"},
    53  			Example:    "echo 1 abc {}",
    54  			Short:      "echo echos the value provided by the user",
    55  			Long:       "echo echos the value provided by the user as a proto JSON object with populated with the provided fields and positional arguments",
    56  			PositionalArgs: []*autocliv1.PositionalArgDescriptor{
    57  				{
    58  					ProtoField: "positional1",
    59  				},
    60  				{
    61  					ProtoField: "positional2",
    62  				},
    63  				{
    64  					ProtoField: "positional3_varargs",
    65  					Varargs:    true,
    66  				},
    67  			},
    68  			FlagOptions: map[string]*autocliv1.FlagOptions{
    69  				"u32": {
    70  					Name:      "uint32",
    71  					Shorthand: "u",
    72  					Usage:     "some random uint32",
    73  				},
    74  				"i32": {
    75  					Usage:        "some random int32",
    76  					DefaultValue: "3",
    77  				},
    78  				"u64": {
    79  					Usage:        "some random uint64",
    80  					DefaultValue: "5",
    81  				},
    82  				"deprecated_field": {
    83  					Deprecated: "don't use this",
    84  				},
    85  				"shorthand_deprecated_field": {
    86  					Shorthand:  "s",
    87  					Deprecated: "bad idea",
    88  				},
    89  				"hidden_bool": {
    90  					Hidden: true,
    91  				},
    92  				"a_coin": {
    93  					Usage: "some random coin",
    94  				},
    95  				"duration": {
    96  					Usage: "some random duration",
    97  				},
    98  				"bz": {
    99  					Usage: "some bytes",
   100  				},
   101  				"map_string_string": {
   102  					Usage: "some map of string to string",
   103  				},
   104  				"map_string_uint32": {
   105  					Usage: "some map of string to int32",
   106  				},
   107  				"map_string_coin": {
   108  					Usage: "some map of string to coin",
   109  				},
   110  			},
   111  		},
   112  	},
   113  	SubCommands: map[string]*autocliv1.ServiceCommandDescriptor{
   114  		// we test the sub-command functionality using the same service with different options
   115  		"deprecatedecho": {
   116  			Service: testpb.Query_ServiceDesc.ServiceName,
   117  			RpcCommandOptions: []*autocliv1.RpcCommandOptions{
   118  				{
   119  					RpcMethod:  "Echo",
   120  					Deprecated: "don't use this",
   121  				},
   122  			},
   123  		},
   124  		"skipecho": {
   125  			Service: testpb.Query_ServiceDesc.ServiceName,
   126  			RpcCommandOptions: []*autocliv1.RpcCommandOptions{
   127  				{
   128  					RpcMethod: "Echo",
   129  					Skip:      true,
   130  				},
   131  			},
   132  		},
   133  	},
   134  }
   135  
   136  var testCmdDescOptional = &autocliv1.ServiceCommandDescriptor{
   137  	Service: testpb.Query_ServiceDesc.ServiceName,
   138  	RpcCommandOptions: []*autocliv1.RpcCommandOptions{
   139  		{
   140  			RpcMethod:  "Echo",
   141  			Use:        "echo [pos1] [pos2] [pos3...]",
   142  			Version:    "1.0",
   143  			Alias:      []string{"e"},
   144  			SuggestFor: []string{"eco"},
   145  			Example:    "echo 1 abc {}",
   146  			Short:      "echo echos the value provided by the user",
   147  			Long:       "echo echos the value provided by the user as a proto JSON object with populated with the provided fields and positional arguments",
   148  			PositionalArgs: []*autocliv1.PositionalArgDescriptor{
   149  				{
   150  					ProtoField: "positional1",
   151  				},
   152  				{
   153  					ProtoField: "positional2",
   154  					Optional:   true,
   155  				},
   156  			},
   157  		},
   158  	},
   159  }
   160  
   161  var testCmdDescInvalidOptAndVargas = &autocliv1.ServiceCommandDescriptor{
   162  	Service: testpb.Query_ServiceDesc.ServiceName,
   163  	RpcCommandOptions: []*autocliv1.RpcCommandOptions{
   164  		{
   165  			RpcMethod:  "Echo",
   166  			Use:        "echo [pos1] [pos2] [pos3...]",
   167  			Version:    "1.0",
   168  			Alias:      []string{"e"},
   169  			SuggestFor: []string{"eco"},
   170  			Example:    "echo 1 abc {}",
   171  			Short:      "echo echos the value provided by the user",
   172  			Long:       "echo echos the value provided by the user as a proto JSON object with populated with the provided fields and positional arguments",
   173  			PositionalArgs: []*autocliv1.PositionalArgDescriptor{
   174  				{
   175  					ProtoField: "positional1",
   176  				},
   177  				{
   178  					ProtoField: "positional2",
   179  					Optional:   true,
   180  				},
   181  				{
   182  					ProtoField: "positional3_varargs",
   183  					Varargs:    true,
   184  				},
   185  			},
   186  		},
   187  	},
   188  }
   189  
   190  func TestCoin(t *testing.T) {
   191  	fixture := initFixture(t)
   192  
   193  	_, err := runCmd(fixture.conn, fixture.b, buildModuleQueryCommand,
   194  		"echo",
   195  		"1",
   196  		"abc",
   197  		"1234foo,4321bar",
   198  		"100uatom",
   199  		"--a-coin", "100000foo",
   200  	)
   201  	assert.ErrorContains(t, err, "coin flag must be a single coin, specific multiple coins with multiple flags or spaces")
   202  
   203  	_, err = runCmd(fixture.conn, fixture.b, buildModuleQueryCommand,
   204  		"echo",
   205  		"1",
   206  		"abc",
   207  		"1234foo",
   208  		"4321bar",
   209  		"100uatom",
   210  		"--a-coin", "100000foo",
   211  		"--coins", "100000bar",
   212  		"--coins", "100uatom",
   213  	)
   214  	assert.NilError(t, err)
   215  	assert.DeepEqual(t, fixture.conn.lastRequest, fixture.conn.lastResponse.(*testpb.EchoResponse).Request, protocmp.Transform())
   216  	expectedResp := &testpb.EchoResponse{
   217  		Request: &testpb.EchoRequest{
   218  			Positional1: 1,
   219  			Positional2: "abc",
   220  			Positional3Varargs: []*basev1beta1.Coin{
   221  				{Amount: "1234", Denom: "foo"},
   222  				{Amount: "4321", Denom: "bar"},
   223  				{Amount: "100", Denom: "uatom"},
   224  			},
   225  			ACoin: &basev1beta1.Coin{
   226  				Amount: "100000",
   227  				Denom:  "foo",
   228  			},
   229  			Coins: []*basev1beta1.Coin{
   230  				{Amount: "100000", Denom: "bar"},
   231  				{Amount: "100", Denom: "uatom"},
   232  			},
   233  			Page: &queryv1beta1.PageRequest{},
   234  			I32:  3,
   235  			U64:  5,
   236  		},
   237  	}
   238  	assert.DeepEqual(t, fixture.conn.lastResponse.(*testpb.EchoResponse), expectedResp, protocmp.Transform())
   239  }
   240  
   241  func TestOptional(t *testing.T) {
   242  	fixture := initFixture(t)
   243  
   244  	_, err := runCmd(fixture.conn, fixture.b, buildModuleQueryCommandOptional,
   245  		"echo",
   246  		"1",
   247  		"abc",
   248  	)
   249  	assert.NilError(t, err)
   250  	request := fixture.conn.lastRequest.(*testpb.EchoRequest)
   251  	assert.Equal(t, request.Positional2, "abc")
   252  	assert.DeepEqual(t, fixture.conn.lastRequest, fixture.conn.lastResponse.(*testpb.EchoResponse).Request, protocmp.Transform())
   253  
   254  	_, err = runCmd(fixture.conn, fixture.b, buildModuleQueryCommandOptional,
   255  		"echo",
   256  		"1",
   257  	)
   258  	assert.NilError(t, err)
   259  
   260  	request = fixture.conn.lastRequest.(*testpb.EchoRequest)
   261  	assert.Equal(t, request.Positional2, "")
   262  	assert.DeepEqual(t, fixture.conn.lastRequest, fixture.conn.lastResponse.(*testpb.EchoResponse).Request, protocmp.Transform())
   263  
   264  	_, err = runCmd(fixture.conn, fixture.b, buildModuleQueryCommandOptional,
   265  		"echo",
   266  		"1",
   267  		"abc",
   268  		"extra-arg",
   269  	)
   270  	assert.ErrorContains(t, err, "accepts between 1 and 2 arg(s), received 3")
   271  
   272  	_, err = runCmd(fixture.conn, fixture.b, buildModuleVargasOptional,
   273  		"echo",
   274  		"1",
   275  		"abc",
   276  		"extra-arg",
   277  	)
   278  	assert.ErrorContains(t, err, "optional positional argument positional2 must be the last argument")
   279  }
   280  
   281  func TestMap(t *testing.T) {
   282  	fixture := initFixture(t)
   283  
   284  	_, err := runCmd(fixture.conn, fixture.b, buildModuleQueryCommand,
   285  		"echo",
   286  		"1",
   287  		"abc",
   288  		"1234foo",
   289  		"4321bar",
   290  		"--map-string-uint32", "bar=123",
   291  		"--map-string-string", "val=foo",
   292  		"--map-string-coin", "baz=100000foo",
   293  		"--map-string-coin", "sec=100000bar",
   294  		"--map-string-coin", "multi=100000bar,flag=100000foo",
   295  	)
   296  	assert.NilError(t, err)
   297  	assert.DeepEqual(t, fixture.conn.lastRequest, fixture.conn.lastResponse.(*testpb.EchoResponse).Request, protocmp.Transform())
   298  
   299  	_, err = runCmd(fixture.conn, fixture.b, buildModuleQueryCommand,
   300  		"echo",
   301  		"1",
   302  		"abc",
   303  		"1234foo",
   304  		"4321bar",
   305  		"--map-string-uint32", "bar=123",
   306  		"--map-string-coin", "baz,100000foo",
   307  		"--map-string-coin", "sec=100000bar",
   308  	)
   309  	assert.ErrorContains(t, err, "invalid argument \"baz,100000foo\" for \"--map-string-coin\" flag: invalid format, expected key=value")
   310  
   311  	_, err = runCmd(fixture.conn, fixture.b, buildModuleQueryCommand,
   312  		"echo",
   313  		"1",
   314  		"abc",
   315  		"1234foo",
   316  		"4321bar",
   317  		"--map-string-uint32", "bar=not-unint32",
   318  		"--map-string-coin", "baz=100000foo",
   319  		"--map-string-coin", "sec=100000bar",
   320  	)
   321  	assert.ErrorContains(t, err, "invalid argument \"bar=not-unint32\" for \"--map-string-uint32\" flag: strconv.ParseUint: parsing \"not-unint32\": invalid syntax")
   322  
   323  	_, err = runCmd(fixture.conn, fixture.b, buildModuleQueryCommand,
   324  		"echo",
   325  		"1",
   326  		"abc",
   327  		"1234foo",
   328  		"4321bar",
   329  		"--map-string-uint32", "bar=123.9",
   330  		"--map-string-coin", "baz=100000foo",
   331  		"--map-string-coin", "sec=100000bar",
   332  	)
   333  	assert.ErrorContains(t, err, "invalid argument \"bar=123.9\" for \"--map-string-uint32\" flag: strconv.ParseUint: parsing \"123.9\": invalid syntax")
   334  }
   335  
   336  // TestEveything tests all the different types of flags are correctly read and as well as correctly returned
   337  // This tests the flag binding and the message building
   338  func TestEverything(t *testing.T) {
   339  	fixture := initFixture(t)
   340  
   341  	_, err := runCmd(fixture.conn, fixture.b, buildModuleQueryCommand,
   342  		"echo",
   343  		"1",
   344  		"abc",
   345  		"123.123123124foo",
   346  		"4321bar",
   347  		"--a-bool",
   348  		"--an-enum", "one",
   349  		"--a-message", `{"bar":"abc", "baz":-3}`,
   350  		"--duration", "4h3s",
   351  		"--uint32", "27",
   352  		"--u64", "3267246890",
   353  		"--i32", "-253",
   354  		"--i64", "-234602347",
   355  		"--str", "def",
   356  		"--timestamp", "2019-01-02T00:01:02Z",
   357  		"--a-coin", "100000foo",
   358  		"--an-address", "cosmos1y74p8wyy4enfhfn342njve6cjmj5c8dtl6emdk",
   359  		"--a-validator-address", "cosmosvaloper1tnh2q55v8wyygtt9srz5safamzdengsn9dsd7z",
   360  		"--a-consensus-address", "cosmosvalcons16vm0nx49eam4q0xasdnwdzsdl6ymgyjt757sgr",
   361  		"--bz", "c2RncXdlZndkZ3NkZw==",
   362  		"--page-count-total",
   363  		"--page-key", "MTIzNTQ4N3NnaGRhcw==",
   364  		"--page-limit", "1000",
   365  		"--page-offset", "10",
   366  		"--page-reverse",
   367  		"--bools", "true",
   368  		"--bools", "false,false,true",
   369  		"--enums", "one",
   370  		"--enums", "five",
   371  		"--enums", "two",
   372  		"--strings", "abc",
   373  		"--strings", "xyz",
   374  		"--strings", "xyz,qrs",
   375  		"--durations", "3s",
   376  		"--durations", "5s",
   377  		"--durations", "10h",
   378  		"--some-messages", "{}",
   379  		"--some-messages", `{"bar":"baz"}`,
   380  		"--some-messages", `{"baz":-1}`,
   381  		"--uints", "1,2,3",
   382  		"--uints", "4",
   383  	)
   384  	assert.NilError(t, err)
   385  
   386  	expectedResp := &testpb.EchoResponse{
   387  		Request: &testpb.EchoRequest{
   388  			Positional1: 1,
   389  			Positional2: "abc",
   390  			Positional3Varargs: []*basev1beta1.Coin{
   391  				{Amount: "123.123123124", Denom: "foo"},
   392  				{Amount: "4321", Denom: "bar"},
   393  			},
   394  			ABool:  true,
   395  			AnEnum: testpb.Enum_ENUM_ONE,
   396  			AMessage: &testpb.AMessage{
   397  				Bar: "abc",
   398  				Baz: -3,
   399  			},
   400  			Duration: durationpb.New(4*time.Hour + 3*time.Second),
   401  			U32:      27,
   402  			U64:      3267246890,
   403  			I32:      -253,
   404  			I64:      -234602347,
   405  			Str:      "def",
   406  			Timestamp: &timestamppb.Timestamp{
   407  				Seconds: 1546387262,
   408  			},
   409  			ACoin: &basev1beta1.Coin{
   410  				Amount: "100000",
   411  				Denom:  "foo",
   412  			},
   413  			AnAddress:         "cosmos1y74p8wyy4enfhfn342njve6cjmj5c8dtl6emdk",
   414  			AValidatorAddress: "cosmosvaloper1tnh2q55v8wyygtt9srz5safamzdengsn9dsd7z",
   415  			AConsensusAddress: "cosmosvalcons16vm0nx49eam4q0xasdnwdzsdl6ymgyjt757sgr",
   416  			Bz:                []byte("sdgqwefwdgsdg"),
   417  			Page: &queryv1beta1.PageRequest{
   418  				CountTotal: true,
   419  				Key:        []byte("1235487sghdas"),
   420  				Limit:      1000,
   421  				Offset:     10,
   422  				Reverse:    true,
   423  			},
   424  			Bools: []bool{true, false, false, true},
   425  			Enums: []testpb.Enum{testpb.Enum_ENUM_ONE, testpb.Enum_ENUM_FIVE, testpb.Enum_ENUM_TWO},
   426  			Strings: []string{
   427  				"abc",
   428  				"xyz",
   429  				"xyz",
   430  				"qrs",
   431  			},
   432  			Durations: []*durationpb.Duration{
   433  				durationpb.New(3 * time.Second),
   434  				durationpb.New(5 * time.Second),
   435  				durationpb.New(10 * time.Hour),
   436  			},
   437  			SomeMessages: []*testpb.AMessage{
   438  				{},
   439  				{Bar: "baz"},
   440  				{Baz: -1},
   441  			},
   442  			Uints: []uint32{1, 2, 3, 4},
   443  		},
   444  	}
   445  
   446  	assert.DeepEqual(t, fixture.conn.lastRequest, fixture.conn.lastResponse.(*testpb.EchoResponse).Request, protocmp.Transform())
   447  	assert.DeepEqual(t, fixture.conn.lastResponse.(*testpb.EchoResponse), expectedResp, protocmp.Transform())
   448  }
   449  
   450  func TestPubKeyParsingConsensusAddress(t *testing.T) {
   451  	fixture := initFixture(t)
   452  
   453  	_, err := runCmd(fixture.conn, fixture.b, buildModuleQueryCommand,
   454  		"echo",
   455  		"1", "abc", "1foo",
   456  		"--a-consensus-address", "{\"@type\":\"/cosmos.crypto.ed25519.PubKey\",\"key\":\"j8qdbR+AlH/V6aBTCSWXRvX3JUESF2bV+SEzndBhF0o=\"}",
   457  		"-u", "27", // shorthand
   458  	)
   459  	assert.NilError(t, err)
   460  	assert.DeepEqual(t, fixture.conn.lastRequest, fixture.conn.lastResponse.(*testpb.EchoResponse).Request, protocmp.Transform())
   461  }
   462  
   463  func TestJSONParsing(t *testing.T) {
   464  	fixture := initFixture(t)
   465  
   466  	_, err := runCmd(fixture.conn, fixture.b, buildModuleQueryCommand,
   467  		"echo",
   468  		"1", "abc", "1foo",
   469  		"--some-messages", `{"bar":"baz"}`,
   470  		"-u", "27", // shorthand
   471  	)
   472  	assert.NilError(t, err)
   473  	assert.DeepEqual(t, fixture.conn.lastRequest, fixture.conn.lastResponse.(*testpb.EchoResponse).Request, protocmp.Transform())
   474  
   475  	_, err = runCmd(fixture.conn, fixture.b, buildModuleQueryCommand,
   476  		"echo",
   477  		"1", "abc", "1foo",
   478  		"--some-messages", "testdata/some_message.json",
   479  		"-u", "27", // shorthand
   480  	)
   481  	assert.NilError(t, err)
   482  	assert.DeepEqual(t, fixture.conn.lastRequest, fixture.conn.lastResponse.(*testpb.EchoResponse).Request, protocmp.Transform())
   483  }
   484  
   485  func TestOptions(t *testing.T) {
   486  	fixture := initFixture(t)
   487  
   488  	_, err := runCmd(fixture.conn, fixture.b, buildModuleQueryCommand,
   489  		"echo",
   490  		"1", "abc", "123foo",
   491  		"-u", "27", // shorthand
   492  		"--u64", "5", // no opt default value
   493  	)
   494  	assert.NilError(t, err)
   495  
   496  	lastReq := fixture.conn.lastRequest.(*testpb.EchoRequest)
   497  	assert.Equal(t, uint32(27), lastReq.U32) // shorthand got set
   498  	assert.Equal(t, int32(3), lastReq.I32)   // default value got set
   499  	assert.Equal(t, uint64(5), lastReq.U64)  // no opt default value got set
   500  }
   501  
   502  func TestBinaryFlag(t *testing.T) {
   503  	// Create a temporary file with some content
   504  	tempFile, err := os.Open("testdata/file.test")
   505  	if err != nil {
   506  		t.Fatal(err)
   507  	}
   508  	content := []byte("this is just a test file")
   509  	if err := tempFile.Close(); err != nil {
   510  		t.Fatal(err)
   511  	}
   512  
   513  	// Test cases
   514  	tests := []struct {
   515  		name     string
   516  		input    string
   517  		expected []byte
   518  		hasError bool
   519  		err      string
   520  	}{
   521  		{
   522  			name:     "Valid file path with extension",
   523  			input:    tempFile.Name(),
   524  			expected: content,
   525  			hasError: false,
   526  			err:      "",
   527  		},
   528  		{
   529  			name:     "Valid hex-encoded string",
   530  			input:    "68656c6c6f20776f726c64",
   531  			expected: []byte("hello world"),
   532  			hasError: false,
   533  			err:      "",
   534  		},
   535  		{
   536  			name:     "Valid base64-encoded string",
   537  			input:    "SGVsbG8gV29ybGQ=",
   538  			expected: []byte("Hello World"),
   539  			hasError: false,
   540  			err:      "",
   541  		},
   542  		{
   543  			name:     "Invalid input (not a file path or encoded string)",
   544  			input:    "not a file or encoded string",
   545  			expected: nil,
   546  			hasError: true,
   547  			err:      "input string is neither a valid file path, hex, or base64 encoded",
   548  		},
   549  	}
   550  
   551  	// Run test cases
   552  	fixture := initFixture(t)
   553  	for _, tc := range tests {
   554  		t.Run(tc.name, func(t *testing.T) {
   555  			_, err := runCmd(fixture.conn, fixture.b, buildModuleQueryCommand,
   556  				"echo",
   557  				"1", "abc", `100foo`,
   558  				"--bz", tc.input,
   559  			)
   560  			if tc.hasError {
   561  				assert.ErrorContains(t, err, tc.err)
   562  			} else {
   563  				assert.NilError(t, err)
   564  				lastReq := fixture.conn.lastRequest.(*testpb.EchoRequest)
   565  				assert.DeepEqual(t, tc.expected, lastReq.Bz)
   566  			}
   567  		})
   568  	}
   569  }
   570  
   571  func TestAddressValidation(t *testing.T) {
   572  	fixture := initFixture(t)
   573  
   574  	_, err := runCmd(fixture.conn, fixture.b, buildModuleQueryCommand,
   575  		"echo",
   576  		"1", "abc", "1foo",
   577  		"--an-address", "cosmos1y74p8wyy4enfhfn342njve6cjmj5c8dtl6emdk",
   578  	)
   579  	assert.NilError(t, err)
   580  
   581  	_, err = runCmd(fixture.conn, fixture.b, buildModuleQueryCommand,
   582  		"echo",
   583  		"1", "abc", "1foo",
   584  		"--an-address", "regen1y74p8wyy4enfhfn342njve6cjmj5c8dtlqj7ule2",
   585  	)
   586  	assert.ErrorContains(t, err, "invalid account address")
   587  
   588  	_, err = runCmd(fixture.conn, fixture.b, buildModuleQueryCommand,
   589  		"echo",
   590  		"1", "abc", "1foo",
   591  		"--an-address", "cosmps1BAD_ENCODING",
   592  	)
   593  	assert.ErrorContains(t, err, "invalid account address")
   594  }
   595  
   596  func TestOutputFormat(t *testing.T) {
   597  	fixture := initFixture(t)
   598  
   599  	out, err := runCmd(fixture.conn, fixture.b, buildModuleQueryCommand,
   600  		"echo",
   601  		"1", "abc", "1foo",
   602  		"--output", "json",
   603  	)
   604  	assert.NilError(t, err)
   605  	assert.Assert(t, strings.Contains(out.String(), "{"))
   606  
   607  	out, err = runCmd(fixture.conn, fixture.b, buildModuleQueryCommand,
   608  		"echo",
   609  		"1", "abc", "1foo",
   610  		"--output", "text",
   611  	)
   612  	assert.NilError(t, err)
   613  	assert.Assert(t, strings.Contains(out.String(), "  positional1: 1"))
   614  }
   615  
   616  func TestHelpQuery(t *testing.T) {
   617  	fixture := initFixture(t)
   618  
   619  	out, err := runCmd(fixture.conn, fixture.b, buildModuleQueryCommand, "-h")
   620  	assert.NilError(t, err)
   621  	golden.Assert(t, out.String(), "help-toplevel.golden")
   622  
   623  	out, err = runCmd(fixture.conn, fixture.b, buildModuleQueryCommand, "echo", "-h")
   624  	assert.NilError(t, err)
   625  	golden.Assert(t, out.String(), "help-echo.golden")
   626  
   627  	out, err = runCmd(fixture.conn, fixture.b, buildModuleQueryCommand, "deprecatedecho", "echo", "-h")
   628  	assert.NilError(t, err)
   629  	golden.Assert(t, out.String(), "help-deprecated.golden")
   630  
   631  	out, err = runCmd(fixture.conn, fixture.b, buildModuleQueryCommand, "skipecho", "-h")
   632  	assert.NilError(t, err)
   633  	golden.Assert(t, out.String(), "help-skip.golden")
   634  }
   635  
   636  func TestDeprecatedQuery(t *testing.T) {
   637  	fixture := initFixture(t)
   638  
   639  	out, err := runCmd(fixture.conn, fixture.b, buildModuleQueryCommand, "echo",
   640  		"1", "abc", "--deprecated-field", "foo")
   641  	assert.NilError(t, err)
   642  	assert.Assert(t, strings.Contains(out.String(), "--deprecated-field has been deprecated"))
   643  
   644  	out, err = runCmd(fixture.conn, fixture.b, buildModuleQueryCommand, "echo",
   645  		"1", "abc", "-s", "foo")
   646  	assert.NilError(t, err)
   647  	assert.Assert(t, strings.Contains(out.String(), "--shorthand-deprecated-field has been deprecated"))
   648  }
   649  
   650  func TestBuildCustomQueryCommand(t *testing.T) {
   651  	b := &Builder{}
   652  	customCommandCalled := false
   653  
   654  	appOptions := AppOptions{
   655  		ModuleOptions: map[string]*autocliv1.ModuleOptions{
   656  			"test": {
   657  				Query: testCmdDesc,
   658  			},
   659  		},
   660  	}
   661  
   662  	cmd, err := b.BuildQueryCommand(appOptions, map[string]*cobra.Command{
   663  		"test": {Use: "test", Run: func(cmd *cobra.Command, args []string) {
   664  			customCommandCalled = true
   665  		}},
   666  	})
   667  	assert.NilError(t, err)
   668  	cmd.SetArgs([]string{"test", "query"})
   669  	assert.NilError(t, cmd.Execute())
   670  	assert.Assert(t, customCommandCalled)
   671  }
   672  
   673  func TestNotFoundErrorsQuery(t *testing.T) {
   674  	fixture := initFixture(t)
   675  	b := fixture.b
   676  	b.AddQueryConnFlags = nil
   677  	b.AddTxConnFlags = nil
   678  
   679  	buildModuleQueryCommand := func(moduleName string, cmdDescriptor *autocliv1.ServiceCommandDescriptor) (*cobra.Command, error) {
   680  		cmd := topLevelCmd("query", "Querying subcommands")
   681  		err := b.AddMsgServiceCommands(cmd, cmdDescriptor)
   682  		return cmd, err
   683  	}
   684  
   685  	// bad service
   686  	_, err := buildModuleQueryCommand("test", &autocliv1.ServiceCommandDescriptor{Service: "foo"})
   687  	assert.ErrorContains(t, err, "can't find service foo")
   688  
   689  	// bad method
   690  	_, err = buildModuleQueryCommand("test", &autocliv1.ServiceCommandDescriptor{
   691  		Service:           testpb.Query_ServiceDesc.ServiceName,
   692  		RpcCommandOptions: []*autocliv1.RpcCommandOptions{{RpcMethod: "bar"}},
   693  	})
   694  	assert.ErrorContains(t, err, "rpc method \"bar\" not found")
   695  
   696  	// bad positional field
   697  	_, err = buildModuleQueryCommand("test", &autocliv1.ServiceCommandDescriptor{
   698  		Service: testpb.Query_ServiceDesc.ServiceName,
   699  		RpcCommandOptions: []*autocliv1.RpcCommandOptions{
   700  			{
   701  				RpcMethod: "Echo",
   702  				PositionalArgs: []*autocliv1.PositionalArgDescriptor{
   703  					{
   704  						ProtoField: "foo",
   705  					},
   706  				},
   707  			},
   708  		},
   709  	})
   710  	assert.ErrorContains(t, err, "can't find field foo")
   711  
   712  	// bad flag field
   713  	_, err = buildModuleQueryCommand("test", &autocliv1.ServiceCommandDescriptor{
   714  		Service: testpb.Query_ServiceDesc.ServiceName,
   715  		RpcCommandOptions: []*autocliv1.RpcCommandOptions{
   716  			{
   717  				RpcMethod: "Echo",
   718  				FlagOptions: map[string]*autocliv1.FlagOptions{
   719  					"baz": {},
   720  				},
   721  			},
   722  		},
   723  	})
   724  	assert.ErrorContains(t, err, "can't find field baz")
   725  }
   726  
   727  func TestDurationMarshal(t *testing.T) {
   728  	fixture := initFixture(t)
   729  
   730  	out, err := runCmd(fixture.conn, fixture.b, buildModuleQueryCommand, "echo", "1", "abc", "--duration", "1s")
   731  	assert.NilError(t, err)
   732  	fmt.Println(out.String())
   733  	assert.Assert(t, strings.Contains(out.String(), "duration: 1s"))
   734  }