github.com/btcsuite/btcd@v0.24.0/btcjson/cmdparse_test.go (about)

     1  // Copyright (c) 2014 The btcsuite developers
     2  // Use of this source code is governed by an ISC
     3  // license that can be found in the LICENSE file.
     4  
     5  package btcjson_test
     6  
     7  import (
     8  	"encoding/json"
     9  	"math"
    10  	"reflect"
    11  	"testing"
    12  
    13  	"github.com/btcsuite/btcd/btcjson"
    14  )
    15  
    16  // TestAssignField tests the assignField function handles supported combinations
    17  // properly.
    18  func TestAssignField(t *testing.T) {
    19  	t.Parallel()
    20  
    21  	tests := []struct {
    22  		name     string
    23  		dest     interface{}
    24  		src      interface{}
    25  		expected interface{}
    26  	}{
    27  		{
    28  			name:     "same types",
    29  			dest:     int8(0),
    30  			src:      int8(100),
    31  			expected: int8(100),
    32  		},
    33  		{
    34  			name: "same types - more source pointers",
    35  			dest: int8(0),
    36  			src: func() interface{} {
    37  				i := int8(100)
    38  				return &i
    39  			}(),
    40  			expected: int8(100),
    41  		},
    42  		{
    43  			name: "same types - more dest pointers",
    44  			dest: func() interface{} {
    45  				i := int8(0)
    46  				return &i
    47  			}(),
    48  			src:      int8(100),
    49  			expected: int8(100),
    50  		},
    51  		{
    52  			name: "convertible types - more source pointers",
    53  			dest: int16(0),
    54  			src: func() interface{} {
    55  				i := int8(100)
    56  				return &i
    57  			}(),
    58  			expected: int16(100),
    59  		},
    60  		{
    61  			name: "convertible types - both pointers",
    62  			dest: func() interface{} {
    63  				i := int8(0)
    64  				return &i
    65  			}(),
    66  			src: func() interface{} {
    67  				i := int16(100)
    68  				return &i
    69  			}(),
    70  			expected: int8(100),
    71  		},
    72  		{
    73  			name:     "convertible types - int16 -> int8",
    74  			dest:     int8(0),
    75  			src:      int16(100),
    76  			expected: int8(100),
    77  		},
    78  		{
    79  			name:     "convertible types - int16 -> uint8",
    80  			dest:     uint8(0),
    81  			src:      int16(100),
    82  			expected: uint8(100),
    83  		},
    84  		{
    85  			name:     "convertible types - uint16 -> int8",
    86  			dest:     int8(0),
    87  			src:      uint16(100),
    88  			expected: int8(100),
    89  		},
    90  		{
    91  			name:     "convertible types - uint16 -> uint8",
    92  			dest:     uint8(0),
    93  			src:      uint16(100),
    94  			expected: uint8(100),
    95  		},
    96  		{
    97  			name:     "convertible types - float32 -> float64",
    98  			dest:     float64(0),
    99  			src:      float32(1.5),
   100  			expected: float64(1.5),
   101  		},
   102  		{
   103  			name:     "convertible types - float64 -> float32",
   104  			dest:     float32(0),
   105  			src:      float64(1.5),
   106  			expected: float32(1.5),
   107  		},
   108  		{
   109  			name:     "convertible types - string -> bool",
   110  			dest:     false,
   111  			src:      "true",
   112  			expected: true,
   113  		},
   114  		{
   115  			name:     "convertible types - string -> int8",
   116  			dest:     int8(0),
   117  			src:      "100",
   118  			expected: int8(100),
   119  		},
   120  		{
   121  			name:     "convertible types - string -> uint8",
   122  			dest:     uint8(0),
   123  			src:      "100",
   124  			expected: uint8(100),
   125  		},
   126  		{
   127  			name:     "convertible types - string -> float32",
   128  			dest:     float32(0),
   129  			src:      "1.5",
   130  			expected: float32(1.5),
   131  		},
   132  		{
   133  			name: "convertible types - typecase string -> string",
   134  			dest: "",
   135  			src: func() interface{} {
   136  				type foo string
   137  				return foo("foo")
   138  			}(),
   139  			expected: "foo",
   140  		},
   141  		{
   142  			name:     "convertible types - string -> array",
   143  			dest:     [2]string{},
   144  			src:      `["test","test2"]`,
   145  			expected: [2]string{"test", "test2"},
   146  		},
   147  		{
   148  			name:     "convertible types - string -> slice",
   149  			dest:     []string{},
   150  			src:      `["test","test2"]`,
   151  			expected: []string{"test", "test2"},
   152  		},
   153  		{
   154  			name:     "convertible types - string -> struct",
   155  			dest:     struct{ A int }{},
   156  			src:      `{"A":100}`,
   157  			expected: struct{ A int }{100},
   158  		},
   159  		{
   160  			name:     "convertible types - string -> map",
   161  			dest:     map[string]float64{},
   162  			src:      `{"1Address":1.5}`,
   163  			expected: map[string]float64{"1Address": 1.5},
   164  		},
   165  		{
   166  			name:     `null optional field - "null" -> *int32`,
   167  			dest:     btcjson.Int32(0),
   168  			src:      "null",
   169  			expected: nil,
   170  		},
   171  		{
   172  			name:     `null optional field - "null" -> *string`,
   173  			dest:     btcjson.String(""),
   174  			src:      "null",
   175  			expected: nil,
   176  		},
   177  	}
   178  
   179  	t.Logf("Running %d tests", len(tests))
   180  	for i, test := range tests {
   181  		dst := reflect.New(reflect.TypeOf(test.dest)).Elem()
   182  		src := reflect.ValueOf(test.src)
   183  		err := btcjson.TstAssignField(1, "testField", dst, src)
   184  		if err != nil {
   185  			t.Errorf("Test #%d (%s) unexpected error: %v", i,
   186  				test.name, err)
   187  			continue
   188  		}
   189  
   190  		// Check case where null string is used on optional field
   191  		if dst.Kind() == reflect.Ptr && test.src == "null" {
   192  			if !dst.IsNil() {
   193  				t.Errorf("Test #%d (%s) unexpected value - got %v, "+
   194  					"want nil", i, test.name, dst.Interface())
   195  			}
   196  			continue
   197  		}
   198  
   199  		// Inidirect through to the base types to ensure their values
   200  		// are the same.
   201  		for dst.Kind() == reflect.Ptr {
   202  			dst = dst.Elem()
   203  		}
   204  		if !reflect.DeepEqual(dst.Interface(), test.expected) {
   205  			t.Errorf("Test #%d (%s) unexpected value - got %v, "+
   206  				"want %v", i, test.name, dst.Interface(),
   207  				test.expected)
   208  			continue
   209  		}
   210  	}
   211  }
   212  
   213  // TestAssignFieldErrors tests the assignField function error paths.
   214  func TestAssignFieldErrors(t *testing.T) {
   215  	t.Parallel()
   216  
   217  	tests := []struct {
   218  		name string
   219  		dest interface{}
   220  		src  interface{}
   221  		err  btcjson.Error
   222  	}{
   223  		{
   224  			name: "general incompatible int -> string",
   225  			dest: "\x00",
   226  			src:  int(0),
   227  			err:  btcjson.Error{ErrorCode: btcjson.ErrInvalidType},
   228  		},
   229  		{
   230  			name: "overflow source int -> dest int",
   231  			dest: int8(0),
   232  			src:  int(128),
   233  			err:  btcjson.Error{ErrorCode: btcjson.ErrInvalidType},
   234  		},
   235  		{
   236  			name: "overflow source int -> dest uint",
   237  			dest: uint8(0),
   238  			src:  int(256),
   239  			err:  btcjson.Error{ErrorCode: btcjson.ErrInvalidType},
   240  		},
   241  		{
   242  			name: "int -> float",
   243  			dest: float32(0),
   244  			src:  int(256),
   245  			err:  btcjson.Error{ErrorCode: btcjson.ErrInvalidType},
   246  		},
   247  		{
   248  			name: "overflow source uint64 -> dest int64",
   249  			dest: int64(0),
   250  			src:  uint64(1 << 63),
   251  			err:  btcjson.Error{ErrorCode: btcjson.ErrInvalidType},
   252  		},
   253  		{
   254  			name: "overflow source uint -> dest int",
   255  			dest: int8(0),
   256  			src:  uint(128),
   257  			err:  btcjson.Error{ErrorCode: btcjson.ErrInvalidType},
   258  		},
   259  		{
   260  			name: "overflow source uint -> dest uint",
   261  			dest: uint8(0),
   262  			src:  uint(256),
   263  			err:  btcjson.Error{ErrorCode: btcjson.ErrInvalidType},
   264  		},
   265  		{
   266  			name: "uint -> float",
   267  			dest: float32(0),
   268  			src:  uint(256),
   269  			err:  btcjson.Error{ErrorCode: btcjson.ErrInvalidType},
   270  		},
   271  		{
   272  			name: "float -> int",
   273  			dest: int(0),
   274  			src:  float32(1.0),
   275  			err:  btcjson.Error{ErrorCode: btcjson.ErrInvalidType},
   276  		},
   277  		{
   278  			name: "overflow float64 -> float32",
   279  			dest: float32(0),
   280  			src:  float64(math.MaxFloat64),
   281  			err:  btcjson.Error{ErrorCode: btcjson.ErrInvalidType},
   282  		},
   283  		{
   284  			name: "invalid string -> bool",
   285  			dest: true,
   286  			src:  "foo",
   287  			err:  btcjson.Error{ErrorCode: btcjson.ErrInvalidType},
   288  		},
   289  		{
   290  			name: "invalid string -> int",
   291  			dest: int8(0),
   292  			src:  "foo",
   293  			err:  btcjson.Error{ErrorCode: btcjson.ErrInvalidType},
   294  		},
   295  		{
   296  			name: "overflow string -> int",
   297  			dest: int8(0),
   298  			src:  "128",
   299  			err:  btcjson.Error{ErrorCode: btcjson.ErrInvalidType},
   300  		},
   301  		{
   302  			name: "invalid string -> uint",
   303  			dest: uint8(0),
   304  			src:  "foo",
   305  			err:  btcjson.Error{ErrorCode: btcjson.ErrInvalidType},
   306  		},
   307  		{
   308  			name: "overflow string -> uint",
   309  			dest: uint8(0),
   310  			src:  "256",
   311  			err:  btcjson.Error{ErrorCode: btcjson.ErrInvalidType},
   312  		},
   313  		{
   314  			name: "invalid string -> float",
   315  			dest: float32(0),
   316  			src:  "foo",
   317  			err:  btcjson.Error{ErrorCode: btcjson.ErrInvalidType},
   318  		},
   319  		{
   320  			name: "overflow string -> float",
   321  			dest: float32(0),
   322  			src:  "1.7976931348623157e+308",
   323  			err:  btcjson.Error{ErrorCode: btcjson.ErrInvalidType},
   324  		},
   325  		{
   326  			name: "invalid string -> array",
   327  			dest: [3]int{},
   328  			src:  "foo",
   329  			err:  btcjson.Error{ErrorCode: btcjson.ErrInvalidType},
   330  		},
   331  		{
   332  			name: "invalid string -> slice",
   333  			dest: []int{},
   334  			src:  "foo",
   335  			err:  btcjson.Error{ErrorCode: btcjson.ErrInvalidType},
   336  		},
   337  		{
   338  			name: "invalid string -> struct",
   339  			dest: struct{ A int }{},
   340  			src:  "foo",
   341  			err:  btcjson.Error{ErrorCode: btcjson.ErrInvalidType},
   342  		},
   343  		{
   344  			name: "invalid string -> map",
   345  			dest: map[string]int{},
   346  			src:  "foo",
   347  			err:  btcjson.Error{ErrorCode: btcjson.ErrInvalidType},
   348  		},
   349  	}
   350  
   351  	t.Logf("Running %d tests", len(tests))
   352  	for i, test := range tests {
   353  		dst := reflect.New(reflect.TypeOf(test.dest)).Elem()
   354  		src := reflect.ValueOf(test.src)
   355  		err := btcjson.TstAssignField(1, "testField", dst, src)
   356  		if reflect.TypeOf(err) != reflect.TypeOf(test.err) {
   357  			t.Errorf("Test #%d (%s) wrong error - got %T (%[3]v), "+
   358  				"want %T", i, test.name, err, test.err)
   359  			continue
   360  		}
   361  		gotErrorCode := err.(btcjson.Error).ErrorCode
   362  		if gotErrorCode != test.err.ErrorCode {
   363  			t.Errorf("Test #%d (%s) mismatched error code - got "+
   364  				"%v (%v), want %v", i, test.name, gotErrorCode,
   365  				err, test.err.ErrorCode)
   366  			continue
   367  		}
   368  	}
   369  }
   370  
   371  // TestNewCmdErrors ensures the error paths of NewCmd behave as expected.
   372  func TestNewCmdErrors(t *testing.T) {
   373  	t.Parallel()
   374  
   375  	tests := []struct {
   376  		name   string
   377  		method string
   378  		args   []interface{}
   379  		err    btcjson.Error
   380  	}{
   381  		{
   382  			name:   "unregistered command",
   383  			method: "boguscommand",
   384  			args:   []interface{}{},
   385  			err:    btcjson.Error{ErrorCode: btcjson.ErrUnregisteredMethod},
   386  		},
   387  		{
   388  			name:   "too few parameters to command with required + optional",
   389  			method: "getblock",
   390  			args:   []interface{}{},
   391  			err:    btcjson.Error{ErrorCode: btcjson.ErrNumParams},
   392  		},
   393  		{
   394  			name:   "too many parameters to command with no optional",
   395  			method: "getblockcount",
   396  			args:   []interface{}{"123"},
   397  			err:    btcjson.Error{ErrorCode: btcjson.ErrNumParams},
   398  		},
   399  		{
   400  			name:   "incorrect parameter type",
   401  			method: "getblock",
   402  			args:   []interface{}{1},
   403  			err:    btcjson.Error{ErrorCode: btcjson.ErrInvalidType},
   404  		},
   405  	}
   406  
   407  	t.Logf("Running %d tests", len(tests))
   408  	for i, test := range tests {
   409  		_, err := btcjson.NewCmd(test.method, test.args...)
   410  		if reflect.TypeOf(err) != reflect.TypeOf(test.err) {
   411  			t.Errorf("Test #%d (%s) wrong error - got %T (%v), "+
   412  				"want %T", i, test.name, err, err, test.err)
   413  			continue
   414  		}
   415  		gotErrorCode := err.(btcjson.Error).ErrorCode
   416  		if gotErrorCode != test.err.ErrorCode {
   417  			t.Errorf("Test #%d (%s) mismatched error code - got "+
   418  				"%v (%v), want %v", i, test.name, gotErrorCode,
   419  				err, test.err.ErrorCode)
   420  			continue
   421  		}
   422  	}
   423  }
   424  
   425  // TestMarshalCmd tests the MarshalCmd function.
   426  func TestMarshalCmd(t *testing.T) {
   427  	t.Parallel()
   428  
   429  	tests := []struct {
   430  		name     string
   431  		id       interface{}
   432  		cmd      interface{}
   433  		expected string
   434  	}{
   435  		{
   436  			name:     "include all parameters",
   437  			id:       1,
   438  			cmd:      btcjson.NewGetNetworkHashPSCmd(btcjson.Int(100), btcjson.Int(2000)),
   439  			expected: `{"jsonrpc":"1.0","method":"getnetworkhashps","params":[100,2000],"id":1}`,
   440  		},
   441  		{
   442  			name:     "include padding null parameter",
   443  			id:       1,
   444  			cmd:      btcjson.NewGetNetworkHashPSCmd(nil, btcjson.Int(2000)),
   445  			expected: `{"jsonrpc":"1.0","method":"getnetworkhashps","params":[null,2000],"id":1}`,
   446  		},
   447  		{
   448  			name:     "omit single unnecessary null parameter",
   449  			id:       1,
   450  			cmd:      btcjson.NewGetNetworkHashPSCmd(btcjson.Int(100), nil),
   451  			expected: `{"jsonrpc":"1.0","method":"getnetworkhashps","params":[100],"id":1}`,
   452  		},
   453  		{
   454  			name:     "omit unnecessary null parameters",
   455  			id:       1,
   456  			cmd:      btcjson.NewGetNetworkHashPSCmd(nil, nil),
   457  			expected: `{"jsonrpc":"1.0","method":"getnetworkhashps","params":[],"id":1}`,
   458  		},
   459  	}
   460  
   461  	t.Logf("Running %d tests", len(tests))
   462  	for i, test := range tests {
   463  		bytes, err := btcjson.MarshalCmd(btcjson.RpcVersion1, test.id, test.cmd)
   464  		if err != nil {
   465  			t.Errorf("Test #%d (%s) wrong error - got %T (%v)",
   466  				i, test.name, err, err)
   467  			continue
   468  		}
   469  		marshalled := string(bytes)
   470  		if marshalled != test.expected {
   471  			t.Errorf("Test #%d (%s) mismatched marshall result - got "+
   472  				"%v, want %v", i, test.name, marshalled, test.expected)
   473  			continue
   474  		}
   475  	}
   476  }
   477  
   478  // TestMarshalCmdErrors  tests the error paths of the MarshalCmd function.
   479  func TestMarshalCmdErrors(t *testing.T) {
   480  	t.Parallel()
   481  
   482  	tests := []struct {
   483  		name string
   484  		id   interface{}
   485  		cmd  interface{}
   486  		err  btcjson.Error
   487  	}{
   488  		{
   489  			name: "unregistered type",
   490  			id:   1,
   491  			cmd:  (*int)(nil),
   492  			err:  btcjson.Error{ErrorCode: btcjson.ErrUnregisteredMethod},
   493  		},
   494  		{
   495  			name: "nil instance of registered type",
   496  			id:   1,
   497  			cmd:  (*btcjson.GetBlockCmd)(nil),
   498  			err:  btcjson.Error{ErrorCode: btcjson.ErrInvalidType},
   499  		},
   500  		{
   501  			name: "nil instance of registered type",
   502  			id:   []int{0, 1},
   503  			cmd:  &btcjson.GetBlockCountCmd{},
   504  			err:  btcjson.Error{ErrorCode: btcjson.ErrInvalidType},
   505  		},
   506  	}
   507  
   508  	t.Logf("Running %d tests", len(tests))
   509  	for i, test := range tests {
   510  		_, err := btcjson.MarshalCmd(btcjson.RpcVersion1, test.id, test.cmd)
   511  		if reflect.TypeOf(err) != reflect.TypeOf(test.err) {
   512  			t.Errorf("Test #%d (%s) wrong error - got %T (%v), "+
   513  				"want %T", i, test.name, err, err, test.err)
   514  			continue
   515  		}
   516  		gotErrorCode := err.(btcjson.Error).ErrorCode
   517  		if gotErrorCode != test.err.ErrorCode {
   518  			t.Errorf("Test #%d (%s) mismatched error code - got "+
   519  				"%v (%v), want %v", i, test.name, gotErrorCode,
   520  				err, test.err.ErrorCode)
   521  			continue
   522  		}
   523  	}
   524  }
   525  
   526  // TestUnmarshalCmdErrors  tests the error paths of the UnmarshalCmd function.
   527  func TestUnmarshalCmdErrors(t *testing.T) {
   528  	t.Parallel()
   529  
   530  	tests := []struct {
   531  		name    string
   532  		request btcjson.Request
   533  		err     btcjson.Error
   534  	}{
   535  		{
   536  			name: "unregistered type",
   537  			request: btcjson.Request{
   538  				Jsonrpc: btcjson.RpcVersion1,
   539  				Method:  "bogusmethod",
   540  				Params:  nil,
   541  				ID:      nil,
   542  			},
   543  			err: btcjson.Error{ErrorCode: btcjson.ErrUnregisteredMethod},
   544  		},
   545  		{
   546  			name: "incorrect number of params",
   547  			request: btcjson.Request{
   548  				Jsonrpc: btcjson.RpcVersion1,
   549  				Method:  "getblockcount",
   550  				Params:  []json.RawMessage{[]byte(`"bogusparam"`)},
   551  				ID:      nil,
   552  			},
   553  			err: btcjson.Error{ErrorCode: btcjson.ErrNumParams},
   554  		},
   555  		{
   556  			name: "invalid type for a parameter",
   557  			request: btcjson.Request{
   558  				Jsonrpc: btcjson.RpcVersion1,
   559  				Method:  "getblock",
   560  				Params:  []json.RawMessage{[]byte("1")},
   561  				ID:      nil,
   562  			},
   563  			err: btcjson.Error{ErrorCode: btcjson.ErrInvalidType},
   564  		},
   565  		{
   566  			name: "invalid JSON for a parameter",
   567  			request: btcjson.Request{
   568  				Jsonrpc: btcjson.RpcVersion1,
   569  				Method:  "getblock",
   570  				Params:  []json.RawMessage{[]byte(`"1`)},
   571  				ID:      nil,
   572  			},
   573  			err: btcjson.Error{ErrorCode: btcjson.ErrInvalidType},
   574  		},
   575  	}
   576  
   577  	t.Logf("Running %d tests", len(tests))
   578  	for i, test := range tests {
   579  		_, err := btcjson.UnmarshalCmd(&test.request)
   580  		if reflect.TypeOf(err) != reflect.TypeOf(test.err) {
   581  			t.Errorf("Test #%d (%s) wrong error - got %T (%v), "+
   582  				"want %T", i, test.name, err, err, test.err)
   583  			continue
   584  		}
   585  		gotErrorCode := err.(btcjson.Error).ErrorCode
   586  		if gotErrorCode != test.err.ErrorCode {
   587  			t.Errorf("Test #%d (%s) mismatched error code - got "+
   588  				"%v (%v), want %v", i, test.name, gotErrorCode,
   589  				err, test.err.ErrorCode)
   590  			continue
   591  		}
   592  	}
   593  }