This commit introduces a new major version of the dcrjson module which
removes all dcrd RPC type support, instead focusing only on method and
type registration. The dcrd methods and types are moved to the
github.com/decred/dcrd/rpc/jsonrpc/types module.
In order to improve backwards compatibility with dcrjson/v2, the API
has been modified to register methods as interface{} instead of
string. This allows different method string types to be used to key
parameter types during registration and lookup, and will allow
dcrjson/v2 to forward registrations of RPC methods to v3 without
causing duplicate registrations errors for incompatible types.
With the introduction of the new types package, the RPC API has been
modified to replace concatenated hash blobs to JSON string arrays of
hash strings. The RPC API major version is bumped to reflect this
change.
A future update to dcrjson/v2 will add additional registrations,
forwarding the registrations to v3 and replacing command types with
type aliases where possible. Unfortunately, this can not be done
entirely in a single commit due to dcrjson/v2 and dcrjson/v3 sharing
the same directory in the source tree, and a branch will need to be
used for this update.
Module replacements are temporarily used to enable the changes for the
main module, including dcrctl. After the aforementioned update to
dcrjson/v2 and a forthcoming update to dcrwallet's RPC types package,
these replacements will be removed.
430 lines
9.4 KiB
Go
430 lines
9.4 KiB
Go
// Copyright (c) 2015 The btcsuite developers
|
|
// Copyright (c) 2015-2019 The Decred developers
|
|
// Use of this source code is governed by an ISC
|
|
// license that can be found in the LICENSE file.
|
|
|
|
package dcrjson
|
|
|
|
import (
|
|
"reflect"
|
|
"testing"
|
|
)
|
|
|
|
// TestCmdMethod tests the CmdMethod function to ensure it returns the expected
|
|
// methods and errors.
|
|
func TestCmdMethod(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
tests := []struct {
|
|
name string
|
|
cmd interface{}
|
|
method string
|
|
err error
|
|
}{
|
|
{
|
|
name: "unregistered type",
|
|
cmd: (*int)(nil),
|
|
err: Error{Code: ErrUnregisteredMethod},
|
|
},
|
|
{
|
|
name: "nil pointer of registered type",
|
|
cmd: (*testGetBlockCmd)(nil),
|
|
method: "getblock",
|
|
},
|
|
{
|
|
name: "zero instance of registered type",
|
|
cmd: &testGetBlockCountCmd{},
|
|
method: "getblockcount",
|
|
},
|
|
}
|
|
|
|
t.Logf("Running %d tests", len(tests))
|
|
for i, test := range tests {
|
|
method, err := CmdMethod(test.cmd)
|
|
if reflect.TypeOf(err) != reflect.TypeOf(test.err) {
|
|
t.Errorf("Test #%d (%s) wrong error - got %T (%[3]v), "+
|
|
"want %T", i, test.name, err, test.err)
|
|
continue
|
|
}
|
|
if err != nil {
|
|
gotErrorCode := err.(Error).Code
|
|
if gotErrorCode != test.err.(Error).Code {
|
|
t.Errorf("Test #%d (%s) mismatched error code "+
|
|
"- got %v (%v), want %v", i, test.name,
|
|
gotErrorCode, err,
|
|
test.err.(Error).Code)
|
|
continue
|
|
}
|
|
|
|
continue
|
|
}
|
|
|
|
// Ensure method matches the expected value.
|
|
if method != test.method {
|
|
t.Errorf("Test #%d (%s) mismatched method - got %v, "+
|
|
"want %v", i, test.name, method, test.method)
|
|
continue
|
|
}
|
|
}
|
|
}
|
|
|
|
// TestMethodUsageFlags tests the MethodUsage function ensure it returns the
|
|
// expected flags and errors.
|
|
func TestMethodUsageFlags(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
tests := []struct {
|
|
name string
|
|
method string
|
|
err error
|
|
flags UsageFlag
|
|
}{
|
|
{
|
|
name: "unregistered type",
|
|
method: "bogusmethod",
|
|
err: Error{Code: ErrUnregisteredMethod},
|
|
},
|
|
{
|
|
name: "getblock",
|
|
method: "getblock",
|
|
flags: 0,
|
|
},
|
|
{
|
|
name: "session",
|
|
method: "session",
|
|
flags: UFWebsocketOnly,
|
|
},
|
|
}
|
|
|
|
t.Logf("Running %d tests", len(tests))
|
|
for i, test := range tests {
|
|
flags, err := MethodUsageFlags(test.method)
|
|
if reflect.TypeOf(err) != reflect.TypeOf(test.err) {
|
|
t.Errorf("Test #%d (%s) wrong error - got %T (%[3]v), "+
|
|
"want %T", i, test.name, err, test.err)
|
|
continue
|
|
}
|
|
if err != nil {
|
|
gotErrorCode := err.(Error).Code
|
|
if gotErrorCode != test.err.(Error).Code {
|
|
t.Errorf("Test #%d (%s) mismatched error code "+
|
|
"- got %v (%v), want %v", i, test.name,
|
|
gotErrorCode, err,
|
|
test.err.(Error).Code)
|
|
continue
|
|
}
|
|
|
|
continue
|
|
}
|
|
|
|
// Ensure flags match the expected value.
|
|
if flags != test.flags {
|
|
t.Errorf("Test #%d (%s) mismatched flags - got %v, "+
|
|
"want %v", i, test.name, flags, test.flags)
|
|
continue
|
|
}
|
|
}
|
|
}
|
|
|
|
// TestMethodUsageText tests the MethodUsageText function ensure it returns the
|
|
// expected text.
|
|
func TestMethodUsageText(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
tests := []struct {
|
|
name string
|
|
method string
|
|
err error
|
|
expected string
|
|
}{
|
|
{
|
|
name: "unregistered type",
|
|
method: "bogusmethod",
|
|
err: Error{Code: ErrUnregisteredMethod},
|
|
},
|
|
{
|
|
name: "getblockcount",
|
|
method: "getblockcount",
|
|
expected: "getblockcount",
|
|
},
|
|
{
|
|
name: "getblock",
|
|
method: "getblock",
|
|
expected: `getblock "hash" (verbose=true verbosetx=false)`,
|
|
},
|
|
}
|
|
|
|
t.Logf("Running %d tests", len(tests))
|
|
for i, test := range tests {
|
|
usage, err := MethodUsageText(test.method)
|
|
if reflect.TypeOf(err) != reflect.TypeOf(test.err) {
|
|
t.Errorf("Test #%d (%s) wrong error - got %T (%[3]v), "+
|
|
"want %T", i, test.name, err, test.err)
|
|
continue
|
|
}
|
|
if err != nil {
|
|
gotErrorCode := err.(Error).Code
|
|
if gotErrorCode != test.err.(Error).Code {
|
|
t.Errorf("Test #%d (%s) mismatched error code "+
|
|
"- got %v (%v), want %v", i, test.name,
|
|
gotErrorCode, err,
|
|
test.err.(Error).Code)
|
|
continue
|
|
}
|
|
|
|
continue
|
|
}
|
|
|
|
// Ensure usage matches the expected value.
|
|
if usage != test.expected {
|
|
t.Errorf("Test #%d (%s) mismatched usage - got %v, "+
|
|
"want %v", i, test.name, usage, test.expected)
|
|
continue
|
|
}
|
|
|
|
// Get the usage again to exercise caching.
|
|
usage, err = MethodUsageText(test.method)
|
|
if err != nil {
|
|
t.Errorf("Test #%d (%s) unexpected error: %v", i,
|
|
test.name, err)
|
|
continue
|
|
}
|
|
|
|
// Ensure usage still matches the expected value.
|
|
if usage != test.expected {
|
|
t.Errorf("Test #%d (%s) mismatched usage - got %v, "+
|
|
"want %v", i, test.name, usage, test.expected)
|
|
continue
|
|
}
|
|
}
|
|
}
|
|
|
|
// TestFieldUsage tests the internal fieldUsage function ensure it returns the
|
|
// expected text.
|
|
func TestFieldUsage(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
tests := []struct {
|
|
name string
|
|
field reflect.StructField
|
|
defValue *reflect.Value
|
|
expected string
|
|
}{
|
|
{
|
|
name: "jsonrpcusage tag override",
|
|
field: func() reflect.StructField {
|
|
type s struct {
|
|
Test int `jsonrpcusage:"testvalue"`
|
|
}
|
|
return reflect.TypeOf((*s)(nil)).Elem().Field(0)
|
|
}(),
|
|
defValue: nil,
|
|
expected: "testvalue",
|
|
},
|
|
{
|
|
name: "generic interface",
|
|
field: func() reflect.StructField {
|
|
type s struct {
|
|
Test interface{}
|
|
}
|
|
return reflect.TypeOf((*s)(nil)).Elem().Field(0)
|
|
}(),
|
|
defValue: nil,
|
|
expected: `test`,
|
|
},
|
|
{
|
|
name: "string without default value",
|
|
field: func() reflect.StructField {
|
|
type s struct {
|
|
Test string
|
|
}
|
|
return reflect.TypeOf((*s)(nil)).Elem().Field(0)
|
|
}(),
|
|
defValue: nil,
|
|
expected: `"test"`,
|
|
},
|
|
{
|
|
name: "string with default value",
|
|
field: func() reflect.StructField {
|
|
type s struct {
|
|
Test string
|
|
}
|
|
return reflect.TypeOf((*s)(nil)).Elem().Field(0)
|
|
}(),
|
|
defValue: func() *reflect.Value {
|
|
value := "default"
|
|
rv := reflect.ValueOf(&value)
|
|
return &rv
|
|
}(),
|
|
expected: `test="default"`,
|
|
},
|
|
{
|
|
name: "array of strings",
|
|
field: func() reflect.StructField {
|
|
type s struct {
|
|
Test []string
|
|
}
|
|
return reflect.TypeOf((*s)(nil)).Elem().Field(0)
|
|
}(),
|
|
defValue: nil,
|
|
expected: `["test",...]`,
|
|
},
|
|
{
|
|
name: "array of strings with plural field name 1",
|
|
field: func() reflect.StructField {
|
|
type s struct {
|
|
Keys []string
|
|
}
|
|
return reflect.TypeOf((*s)(nil)).Elem().Field(0)
|
|
}(),
|
|
defValue: nil,
|
|
expected: `["key",...]`,
|
|
},
|
|
{
|
|
name: "array of strings with plural field name 2",
|
|
field: func() reflect.StructField {
|
|
type s struct {
|
|
Addresses []string
|
|
}
|
|
return reflect.TypeOf((*s)(nil)).Elem().Field(0)
|
|
}(),
|
|
defValue: nil,
|
|
expected: `["address",...]`,
|
|
},
|
|
{
|
|
name: "array of strings with plural field name 3",
|
|
field: func() reflect.StructField {
|
|
type s struct {
|
|
Capabilities []string
|
|
}
|
|
return reflect.TypeOf((*s)(nil)).Elem().Field(0)
|
|
}(),
|
|
defValue: nil,
|
|
expected: `["capability",...]`,
|
|
},
|
|
{
|
|
name: "array of structs",
|
|
field: func() reflect.StructField {
|
|
type s2 struct {
|
|
Txid string
|
|
}
|
|
type s struct {
|
|
Capabilities []s2
|
|
}
|
|
return reflect.TypeOf((*s)(nil)).Elem().Field(0)
|
|
}(),
|
|
defValue: nil,
|
|
expected: `[{"txid":"value"},...]`,
|
|
},
|
|
{
|
|
name: "array of ints",
|
|
field: func() reflect.StructField {
|
|
type s struct {
|
|
Test []int
|
|
}
|
|
return reflect.TypeOf((*s)(nil)).Elem().Field(0)
|
|
}(),
|
|
defValue: nil,
|
|
expected: `[test,...]`,
|
|
},
|
|
{
|
|
name: "sub struct with jsonrpcusage tag override",
|
|
field: func() reflect.StructField {
|
|
type s2 struct {
|
|
Test string `jsonrpcusage:"testusage"`
|
|
}
|
|
type s struct {
|
|
Test s2
|
|
}
|
|
return reflect.TypeOf((*s)(nil)).Elem().Field(0)
|
|
}(),
|
|
defValue: nil,
|
|
expected: `{testusage}`,
|
|
},
|
|
{
|
|
name: "sub struct with string",
|
|
field: func() reflect.StructField {
|
|
type s2 struct {
|
|
Txid string
|
|
}
|
|
type s struct {
|
|
Test s2
|
|
}
|
|
return reflect.TypeOf((*s)(nil)).Elem().Field(0)
|
|
}(),
|
|
defValue: nil,
|
|
expected: `{"txid":"value"}`,
|
|
},
|
|
{
|
|
name: "sub struct with int",
|
|
field: func() reflect.StructField {
|
|
type s2 struct {
|
|
Vout int
|
|
}
|
|
type s struct {
|
|
Test s2
|
|
}
|
|
return reflect.TypeOf((*s)(nil)).Elem().Field(0)
|
|
}(),
|
|
defValue: nil,
|
|
expected: `{"vout":n}`,
|
|
},
|
|
{
|
|
name: "sub struct with float",
|
|
field: func() reflect.StructField {
|
|
type s2 struct {
|
|
Amount float64
|
|
}
|
|
type s struct {
|
|
Test s2
|
|
}
|
|
return reflect.TypeOf((*s)(nil)).Elem().Field(0)
|
|
}(),
|
|
defValue: nil,
|
|
expected: `{"amount":n.nnn}`,
|
|
},
|
|
{
|
|
name: "sub struct with sub struct",
|
|
field: func() reflect.StructField {
|
|
type s3 struct {
|
|
Amount float64
|
|
}
|
|
type s2 struct {
|
|
Template s3
|
|
}
|
|
type s struct {
|
|
Test s2
|
|
}
|
|
return reflect.TypeOf((*s)(nil)).Elem().Field(0)
|
|
}(),
|
|
defValue: nil,
|
|
expected: `{"template":{"amount":n.nnn}}`,
|
|
},
|
|
{
|
|
name: "sub struct with slice",
|
|
field: func() reflect.StructField {
|
|
type s2 struct {
|
|
Capabilities []string
|
|
}
|
|
type s struct {
|
|
Test s2
|
|
}
|
|
return reflect.TypeOf((*s)(nil)).Elem().Field(0)
|
|
}(),
|
|
defValue: nil,
|
|
expected: `{"capabilities":["capability",...]}`,
|
|
},
|
|
}
|
|
|
|
t.Logf("Running %d tests", len(tests))
|
|
for i, test := range tests {
|
|
// Ensure usage matches the expected value.
|
|
usage := fieldUsage(test.field, test.defValue)
|
|
if usage != test.expected {
|
|
t.Errorf("Test #%d (%s) mismatched usage - got %v, "+
|
|
"want %v", i, test.name, usage, test.expected)
|
|
continue
|
|
}
|
|
}
|
|
}
|