cmdparse_test.go raw

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