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 }