decred.org/dcrdex@v1.0.5/client/rpcserver/handlers_test.go (about)

     1  // This code is available on the terms of the project LICENSE.md file,
     2  // also available online at https://blueoakcouncil.org/license/1.0.0.
     3  
     4  package rpcserver
     5  
     6  import (
     7  	"context"
     8  	"encoding/json"
     9  	"errors"
    10  	"fmt"
    11  	"reflect"
    12  	"strings"
    13  	"testing"
    14  
    15  	"decred.org/dcrdex/client/asset"
    16  	"decred.org/dcrdex/client/core"
    17  	"decred.org/dcrdex/client/websocket"
    18  	"decred.org/dcrdex/dex"
    19  	"decred.org/dcrdex/dex/encode"
    20  	"decred.org/dcrdex/dex/msgjson"
    21  	"github.com/davecgh/go-spew/spew"
    22  )
    23  
    24  func init() {
    25  	asset.Register(tUTXOAssetA.ID, &tDriver{
    26  		decodedCoinID: tUTXOAssetA.Symbol,
    27  		winfo:         tWalletInfo,
    28  	})
    29  }
    30  
    31  func verifyResponse(payload *msgjson.ResponsePayload, res any, wantErrCode int) error {
    32  	if wantErrCode != -1 {
    33  		if payload.Error == nil {
    34  			return errors.New("no error")
    35  		}
    36  		if payload.Error.Code != wantErrCode {
    37  			return errors.New("wrong error code")
    38  		}
    39  	} else {
    40  		if payload.Error != nil {
    41  			return fmt.Errorf("unexpected error: %v", payload.Error)
    42  		}
    43  	}
    44  	if err := json.Unmarshal(payload.Result, res); err != nil {
    45  		return fmt.Errorf("unable to unmarshal res: %v", err)
    46  	}
    47  	return nil
    48  }
    49  
    50  var (
    51  	wsServer    = websocket.New(&TCore{}, dex.StdOutLogger("TEST", dex.LevelTrace))
    52  	tUTXOAssetA = &dex.Asset{
    53  		ID:         42,
    54  		Symbol:     "dcr",
    55  		Version:    0, // match the stubbed (*TXCWallet).Info result
    56  		MaxFeeRate: 10,
    57  		SwapConf:   1,
    58  	}
    59  	tWalletInfo = &asset.WalletInfo{
    60  		SupportedVersions: []uint32{0},
    61  		UnitInfo: dex.UnitInfo{
    62  			Conventional: dex.Denomination{
    63  				ConversionFactor: 1e8,
    64  			},
    65  		},
    66  		AvailableWallets: []*asset.WalletDefinition{{
    67  			Type: "rpc",
    68  		}},
    69  	}
    70  )
    71  
    72  type tDriver struct {
    73  	wallet        asset.Wallet
    74  	decodedCoinID string
    75  	winfo         *asset.WalletInfo
    76  }
    77  
    78  func (drv *tDriver) Open(cfg *asset.WalletConfig, logger dex.Logger, net dex.Network) (asset.Wallet, error) {
    79  	return drv.wallet, nil
    80  }
    81  
    82  func (drv *tDriver) DecodeCoinID(coinID []byte) (string, error) {
    83  	return drv.decodedCoinID, nil
    84  }
    85  
    86  func (drv *tDriver) Info() *asset.WalletInfo {
    87  	return drv.winfo
    88  }
    89  
    90  type Dummy struct {
    91  	Status string
    92  }
    93  
    94  func TestCreateResponse(t *testing.T) {
    95  	tests := []struct {
    96  		name        string
    97  		res         any
    98  		resErr      *msgjson.Error
    99  		wantErrCode int
   100  	}{{
   101  		name:        "ok",
   102  		res:         Dummy{"ok"},
   103  		resErr:      nil,
   104  		wantErrCode: -1,
   105  	}, {
   106  		name: "parse error",
   107  		res:  "",
   108  		resErr: msgjson.NewError(msgjson.RPCParseError,
   109  			"failed to encode response"),
   110  		wantErrCode: msgjson.RPCParseError,
   111  	}}
   112  
   113  	for _, test := range tests {
   114  		payload := createResponse(test.name, &test.res, test.resErr)
   115  		if err := verifyResponse(payload, &test.res, test.wantErrCode); err != nil {
   116  			t.Fatal(err)
   117  		}
   118  
   119  	}
   120  }
   121  
   122  func TestHelpMsgs(t *testing.T) {
   123  	// routes and helpMsgs must have the same keys.
   124  	if len(routes) != len(helpMsgs) {
   125  		t.Fatal("routes and helpMsgs have different number of routes")
   126  	}
   127  	for k := range routes {
   128  		if _, exists := helpMsgs[k]; !exists {
   129  			t.Fatalf("%v exists in routes but not in helpMsgs", k)
   130  		}
   131  	}
   132  }
   133  
   134  func TestListCommands(t *testing.T) {
   135  	// no passwords
   136  	res := ListCommands(false)
   137  	if res == "" {
   138  		t.Fatal("unable to parse helpMsgs")
   139  	}
   140  	want := ""
   141  	for _, r := range sortHelpKeys() {
   142  		msg := helpMsgs[r]
   143  		want += r + " " + msg.argsShort + "\n"
   144  	}
   145  	if res != want[:len(want)-1] {
   146  		t.Fatalf("wanted %s but got %s", want, res)
   147  	}
   148  	// with passwords
   149  	res = ListCommands(true)
   150  	if res == "" {
   151  		t.Fatal("unable to parse helpMsgs")
   152  	}
   153  	want = ""
   154  	for _, r := range sortHelpKeys() {
   155  		msg := helpMsgs[r]
   156  		if msg.pwArgsShort != "" {
   157  			want += r + " " + format(msg.pwArgsShort, " ") + msg.argsShort + "\n"
   158  		} else {
   159  			want += r + " " + msg.argsShort + "\n"
   160  		}
   161  	}
   162  	if res != want[:len(want)-1] {
   163  		t.Fatalf("wanted %s but got %s", want, res)
   164  	}
   165  }
   166  
   167  func TestCommandUsage(t *testing.T) {
   168  	for r, msg := range helpMsgs {
   169  		// no passwords
   170  		res, err := commandUsage(r, false)
   171  		if err != nil {
   172  			t.Fatalf("unexpected error for command %s", r)
   173  		}
   174  		want := r + " " + msg.argsShort + "\n\n" + msg.cmdSummary + "\n\n" +
   175  			format(msg.argsLong, "\n\n") + msg.returns
   176  		if res != want {
   177  			t.Fatalf("wanted %s but got %s for usage of %s without passwords", want, res, r)
   178  		}
   179  
   180  		// with passwords when applicable
   181  		if msg.pwArgsShort != "" {
   182  			res, err = commandUsage(r, true)
   183  			if err != nil {
   184  				t.Fatalf("unexpected error for command %s", r)
   185  			}
   186  			want = r + " " + format(msg.pwArgsShort, " ") + msg.argsShort + "\n\n" +
   187  				msg.cmdSummary + "\n\n" + format(msg.pwArgsLong, "\n\n") +
   188  				format(msg.argsLong, "\n\n") + msg.returns
   189  			if res != want {
   190  				t.Fatalf("wanted %s but got %s for usage of %s with passwords", want, res, r)
   191  			}
   192  		}
   193  	}
   194  	if _, err := commandUsage("never make this command", false); !errors.Is(err, errUnknownCmd) {
   195  		t.Fatal("expected error for bogus command")
   196  	}
   197  }
   198  
   199  func TestHandleHelp(t *testing.T) {
   200  	tests := []struct {
   201  		name        string
   202  		params      *RawParams
   203  		wantErrCode int
   204  	}{{
   205  		name:        "ok no arg",
   206  		params:      new(RawParams),
   207  		wantErrCode: -1,
   208  	}, {
   209  		name:        "ok with arg",
   210  		params:      &RawParams{Args: []string{"version"}},
   211  		wantErrCode: -1,
   212  	}, {
   213  		name:        "unknown route",
   214  		params:      &RawParams{Args: []string{"versio"}},
   215  		wantErrCode: msgjson.RPCUnknownRoute,
   216  	}, {
   217  		name:        "bad params",
   218  		params:      &RawParams{Args: []string{"version", "blue"}},
   219  		wantErrCode: msgjson.RPCArgumentsError,
   220  	}}
   221  	for _, test := range tests {
   222  		payload := handleHelp(nil, test.params)
   223  		res := ""
   224  		if err := verifyResponse(payload, &res, test.wantErrCode); err != nil {
   225  			t.Fatal(err)
   226  		}
   227  	}
   228  }
   229  
   230  func TestHandleVersion(t *testing.T) {
   231  	tc := &TCore{}
   232  	r := &RPCServer{core: tc, bwVersion: &SemVersion{}}
   233  	payload := handleVersion(r, nil)
   234  	res := &VersionResponse{}
   235  	if err := verifyResponse(payload, &res, -1); err != nil {
   236  		t.Fatal(err)
   237  	}
   238  }
   239  
   240  func TestHandleGetDEXConfig(t *testing.T) {
   241  	tests := []struct {
   242  		name            string
   243  		params          *RawParams
   244  		getDEXConfigErr error
   245  		wantErrCode     int
   246  	}{{
   247  		name:        "ok",
   248  		params:      &RawParams{Args: []string{"dex", "cert bytes"}},
   249  		wantErrCode: -1,
   250  	}, {
   251  		name:            "get dex conf error",
   252  		params:          &RawParams{Args: []string{"dex", "cert bytes"}},
   253  		getDEXConfigErr: errors.New(""),
   254  		wantErrCode:     msgjson.RPCGetDEXConfigError,
   255  	}, {
   256  		name:        "bad params",
   257  		params:      &RawParams{},
   258  		wantErrCode: msgjson.RPCArgumentsError,
   259  	}}
   260  	for _, test := range tests {
   261  		tc := &TCore{
   262  			getDEXConfigErr: test.getDEXConfigErr,
   263  		}
   264  		r := &RPCServer{core: tc}
   265  		payload := handleGetDEXConfig(r, test.params)
   266  		res := new(*core.Exchange)
   267  		if err := verifyResponse(payload, &res, test.wantErrCode); err != nil {
   268  			t.Fatal(err)
   269  		}
   270  	}
   271  }
   272  
   273  func TestHandleInit(t *testing.T) {
   274  	pw := encode.PassBytes("password123")
   275  	tests := []struct {
   276  		name                string
   277  		params              *RawParams
   278  		initializeClientErr error
   279  		wantErrCode         int
   280  	}{{
   281  		name:        "ok",
   282  		params:      &RawParams{PWArgs: []encode.PassBytes{pw}},
   283  		wantErrCode: -1,
   284  	}, {
   285  		name:                "core.InitializeClient error",
   286  		params:              &RawParams{PWArgs: []encode.PassBytes{pw}},
   287  		initializeClientErr: errors.New("error"),
   288  		wantErrCode:         msgjson.RPCInitError,
   289  	}, {
   290  		name:        "bad params",
   291  		params:      &RawParams{},
   292  		wantErrCode: msgjson.RPCArgumentsError,
   293  	}}
   294  	for _, test := range tests {
   295  		tc := &TCore{initializeClientErr: test.initializeClientErr}
   296  		r := &RPCServer{core: tc}
   297  		payload := handleInit(r, test.params)
   298  		res := ""
   299  		if err := verifyResponse(payload, &res, test.wantErrCode); err != nil {
   300  			t.Fatal(err)
   301  		}
   302  	}
   303  }
   304  
   305  func TestHandleNewWallet(t *testing.T) {
   306  	pw := encode.PassBytes("password123")
   307  	params := &RawParams{
   308  		PWArgs: []encode.PassBytes{pw, pw},
   309  		Args: []string{
   310  			"42",
   311  			"rpc",
   312  			"username=tacotime",
   313  			`{"field":"value"}`,
   314  		},
   315  	}
   316  	badJSONParams := &RawParams{
   317  		PWArgs: []encode.PassBytes{pw, pw},
   318  		Args: []string{
   319  			"42",
   320  			"rpc",
   321  			"username=tacotime",
   322  			`{"field":  value"}`,
   323  		},
   324  	}
   325  	badWalletDefParams := &RawParams{
   326  		PWArgs: []encode.PassBytes{pw, pw},
   327  		Args: []string{
   328  			"45",
   329  			"rpc",
   330  			"username=tacotime",
   331  			`{"field":"value"}`,
   332  		},
   333  	}
   334  	tests := []struct {
   335  		name            string
   336  		params          *RawParams
   337  		walletState     *core.WalletState
   338  		createWalletErr error
   339  		openWalletErr   error
   340  		wantErrCode     int
   341  	}{{
   342  		name:        "ok new wallet",
   343  		params:      params,
   344  		wantErrCode: -1,
   345  	}, {
   346  		name:        "ok existing wallet",
   347  		params:      params,
   348  		walletState: &core.WalletState{Open: false},
   349  		wantErrCode: msgjson.RPCWalletExistsError,
   350  	}, {
   351  		name:            "core.CreateWallet error",
   352  		params:          params,
   353  		createWalletErr: errors.New("error"),
   354  		wantErrCode:     msgjson.RPCCreateWalletError,
   355  	}, {
   356  		name:          "core.OpenWallet error",
   357  		params:        params,
   358  		openWalletErr: errors.New("error"),
   359  		wantErrCode:   msgjson.RPCOpenWalletError,
   360  	}, {
   361  		name:        "bad JSON error",
   362  		params:      badJSONParams,
   363  		wantErrCode: msgjson.RPCArgumentsError,
   364  	}, {
   365  		name:        "bad config opts error, unknown coin",
   366  		params:      badWalletDefParams,
   367  		wantErrCode: msgjson.RPCWalletDefinitionError,
   368  	}, {
   369  		name:        "bad params",
   370  		params:      &RawParams{},
   371  		wantErrCode: msgjson.RPCArgumentsError,
   372  	}}
   373  	for _, test := range tests {
   374  		tc := &TCore{
   375  			walletState:     test.walletState,
   376  			createWalletErr: test.createWalletErr,
   377  			openWalletErr:   test.openWalletErr,
   378  		}
   379  		r := &RPCServer{core: tc, wsServer: wsServer}
   380  		payload := handleNewWallet(r, test.params)
   381  
   382  		res := ""
   383  		if err := verifyResponse(payload, &res, test.wantErrCode); err != nil {
   384  			t.Fatalf("%s: %v", test.name, err)
   385  		}
   386  		if test.wantErrCode == -1 {
   387  			if tc.newWalletForm.AssetID != 42 {
   388  				t.Fatalf("assetID not parsed correctly")
   389  			}
   390  			cfg := tc.newWalletForm.Config
   391  			if cfg["username"] != "tacotime" {
   392  				t.Fatalf("file config not parsed correctly")
   393  			}
   394  			if cfg["field"] != "value" {
   395  				t.Fatalf("custom config not parsed correctly")
   396  			}
   397  		}
   398  	}
   399  }
   400  
   401  func TestHandleOpenWallet(t *testing.T) {
   402  	pw := encode.PassBytes("password123")
   403  	params := &RawParams{
   404  		PWArgs: []encode.PassBytes{pw},
   405  		Args: []string{
   406  			"42",
   407  		},
   408  	}
   409  	tests := []struct {
   410  		name          string
   411  		params        *RawParams
   412  		openWalletErr error
   413  		wantErrCode   int
   414  	}{{
   415  		name:        "ok",
   416  		params:      params,
   417  		wantErrCode: -1,
   418  	}, {
   419  		name:          "core.OpenWallet error",
   420  		params:        params,
   421  		openWalletErr: errors.New("error"),
   422  		wantErrCode:   msgjson.RPCOpenWalletError,
   423  	}, {
   424  		name:        "bad params",
   425  		params:      &RawParams{},
   426  		wantErrCode: msgjson.RPCArgumentsError,
   427  	}}
   428  	for _, test := range tests {
   429  		tc := &TCore{openWalletErr: test.openWalletErr}
   430  		r := &RPCServer{core: tc, wsServer: wsServer}
   431  		payload := handleOpenWallet(r, test.params)
   432  		res := ""
   433  		if err := verifyResponse(payload, &res, test.wantErrCode); err != nil {
   434  			t.Fatal(err)
   435  		}
   436  	}
   437  }
   438  
   439  func TestHandleCloseWallet(t *testing.T) {
   440  	tests := []struct {
   441  		name           string
   442  		params         *RawParams
   443  		closeWalletErr error
   444  		wantErrCode    int
   445  	}{{
   446  		name:        "ok",
   447  		params:      &RawParams{Args: []string{"42"}},
   448  		wantErrCode: -1,
   449  	}, {
   450  		name:           "core.closeWallet error",
   451  		params:         &RawParams{Args: []string{"42"}},
   452  		closeWalletErr: errors.New("error"),
   453  		wantErrCode:    msgjson.RPCCloseWalletError,
   454  	}, {
   455  		name:        "bad params",
   456  		params:      &RawParams{},
   457  		wantErrCode: msgjson.RPCArgumentsError,
   458  	}}
   459  	for _, test := range tests {
   460  		tc := &TCore{closeWalletErr: test.closeWalletErr}
   461  		r := &RPCServer{core: tc, wsServer: wsServer}
   462  		payload := handleCloseWallet(r, test.params)
   463  		res := ""
   464  		if err := verifyResponse(payload, &res, test.wantErrCode); err != nil {
   465  			t.Fatal(err)
   466  		}
   467  	}
   468  }
   469  
   470  func TestHandleToggleWalletStatus(t *testing.T) {
   471  	tests := []struct {
   472  		name            string
   473  		params          *RawParams
   474  		walletStatusErr error
   475  		wantErrCode     int
   476  	}{{
   477  		name:        "ok: disable",
   478  		params:      &RawParams{Args: []string{"42", "true"}},
   479  		wantErrCode: -1,
   480  	}, {
   481  		name:        "ok: enable",
   482  		params:      &RawParams{Args: []string{"42", "false"}},
   483  		wantErrCode: -1,
   484  	}, {
   485  		name:            "core.toggleWalletStatus error",
   486  		params:          &RawParams{Args: []string{"42", "true"}},
   487  		walletStatusErr: errors.New("error"),
   488  		wantErrCode:     msgjson.RPCToggleWalletStatusError,
   489  	}, {
   490  		name:        "bad params",
   491  		params:      &RawParams{Args: []string{"42"}},
   492  		wantErrCode: msgjson.RPCArgumentsError,
   493  	}}
   494  	for _, test := range tests {
   495  		tc := &TCore{walletStatusErr: test.walletStatusErr, walletState: &core.WalletState{}}
   496  		r := &RPCServer{core: tc, wsServer: wsServer}
   497  		payload := handleToggleWalletStatus(r, test.params)
   498  		res := ""
   499  		if err := verifyResponse(payload, &res, test.wantErrCode); err != nil {
   500  			t.Fatalf("%s failed: %v", test.name, err)
   501  		}
   502  	}
   503  }
   504  
   505  func TestHandleWallets(t *testing.T) {
   506  	tc := new(TCore)
   507  	r := &RPCServer{core: tc}
   508  	payload := handleWallets(r, nil)
   509  	res := ""
   510  	if err := verifyResponse(payload, &res, -1); err != nil {
   511  		t.Fatal(err)
   512  	}
   513  }
   514  
   515  const exchangeIn = `{
   516    "https://127.0.0.1:7232": {
   517      "host": "https://127.0.0.1:7232",
   518      "acctID": "edc7620e02",
   519      "markets": {
   520        "dcr_btc": {
   521          "name": "dcr_btc",
   522          "baseid": 42,
   523          "basesymbol": "dcr",
   524          "quoteid": 0,
   525          "quotesymbol": "btc",
   526          "epochlen": 10000,
   527          "startepoch": 158891349,
   528          "buybuffer": 1.25,
   529          "orders": [
   530            {
   531              "dex": "https://127.0.0.1:7232",
   532              "market": "dcr_btc",
   533              "type": 1,
   534              "id": "e016a563ff5b845e9af20718af72224af630e65ca53edf2a3342d175dc6d3738",
   535              "stamp": 1588913556583,
   536              "qty": 100000000,
   537              "sell": false,
   538              "sig": "3045022100c5ef66cbf3c2d305408b666108ae384478f22b558893942b8f66abfb613a5bf802205eb22a0250e5286244b2f5205f0b6d6b4fa6a60930be2ff30f35c3cf6bf969c8",
   539              "filled": 0,
   540              "matches": [
   541                {
   542                  "matchID": "1472deb169fb359a48676161be8ca81983201f28abe8cc9b504950032d6f14ec",
   543                  "qty": 100000000,
   544                  "rate": 100000000,
   545                  "step": 1
   546                }
   547              ],
   548              "cancelling": false,
   549              "canceled": false,
   550              "rate": 100000000,
   551              "tif": 1
   552            }
   553          ]
   554        }
   555      },
   556      "assets": {
   557        "0": {
   558          "id": 0,
   559          "symbol": "btc",
   560          "lotSize": 100000,
   561          "rateStep": 100000,
   562          "maxFeeRate": 100,
   563          "swapSize": 225,
   564          "swapSizeBase": 76,
   565          "swapConf": 1
   566        },
   567        "42": {
   568          "id": 42,
   569          "symbol": "dcr",
   570          "lotSize": 100000000,
   571          "rateStep": 100000000,
   572          "maxFeeRate": 10,
   573          "swapSize": 251,
   574          "swapSizeBase": 85,
   575          "swapConf": 1
   576        }
   577      },
   578      "regFees": {
   579        "dcr": {
   580          "id": 42,
   581          "amt": 2000000,
   582          "confs": 1
   583  	  }
   584  	}
   585    }
   586  }`
   587  
   588  const exchangeOut = `{
   589    "https://127.0.0.1:7232": {
   590      "acctID": "edc7620e02",
   591      "markets": {
   592        "dcr_btc": {
   593          "baseid": 42,
   594          "basesymbol": "dcr",
   595          "quoteid": 0,
   596          "quotesymbol": "btc",
   597          "epochlen": 10000,
   598          "startepoch": 158891349,
   599          "buybuffer": 1.25
   600        }
   601      },
   602      "assets": {
   603        "0": {
   604          "symbol": "btc",
   605          "lotSize": 100000,
   606          "rateStep": 100000,
   607          "maxFeeRate": 100,
   608          "swapSize": 225,
   609          "swapSizeBase": 76,
   610          "swapConf": 1
   611        },
   612        "42": {
   613          "symbol": "dcr",
   614          "lotSize": 100000000,
   615          "rateStep": 100000000,
   616          "maxFeeRate": 10,
   617          "swapSize": 251,
   618          "swapSizeBase": 85,
   619          "swapConf": 1
   620        }
   621      },
   622      "regFees": {
   623        "dcr": {
   624          "id": 42,
   625          "amt": 2000000,
   626          "confs": 1
   627        }
   628      }
   629    }
   630  }`
   631  
   632  func TestHandleExchanges(t *testing.T) {
   633  	var exchangesIn map[string]*core.Exchange
   634  	if err := json.Unmarshal([]byte(exchangeIn), &exchangesIn); err != nil {
   635  		panic(err)
   636  	}
   637  	tc := &TCore{exchanges: exchangesIn}
   638  	r := &RPCServer{core: tc}
   639  	payload := handleExchanges(r, nil)
   640  	var res map[string]*core.Exchange
   641  	if err := verifyResponse(payload, &res, -1); err != nil {
   642  		t.Fatal(err)
   643  	}
   644  	var exchangesOut map[string]*core.Exchange
   645  	if err := json.Unmarshal([]byte(exchangeOut), &exchangesOut); err != nil {
   646  		panic(err)
   647  	}
   648  	if !reflect.DeepEqual(res, exchangesOut) {
   649  		t.Fatalf("expected %v but got %v", spew.Sdump(exchangesOut), spew.Sdump(res))
   650  	}
   651  }
   652  
   653  func TestHandleLogin(t *testing.T) {
   654  	params := &RawParams{PWArgs: []encode.PassBytes{encode.PassBytes("abc")}}
   655  	tests := []struct {
   656  		name        string
   657  		params      *RawParams
   658  		loginErr    error
   659  		wantErrCode int
   660  	}{{
   661  		name:        "ok",
   662  		params:      params,
   663  		wantErrCode: -1,
   664  	}, {
   665  		name:        "core.Login error",
   666  		params:      params,
   667  		loginErr:    errors.New("error"),
   668  		wantErrCode: msgjson.RPCLoginError,
   669  	}, {
   670  		name:        "bad params",
   671  		params:      &RawParams{},
   672  		wantErrCode: msgjson.RPCArgumentsError,
   673  	}}
   674  	for _, test := range tests {
   675  		tc := &TCore{
   676  			loginErr: test.loginErr,
   677  		}
   678  		r := &RPCServer{core: tc}
   679  		payload := handleLogin(r, test.params)
   680  		successString := "successfully logged in"
   681  		if err := verifyResponse(payload, &successString, test.wantErrCode); err != nil {
   682  			t.Fatal(err)
   683  		}
   684  	}
   685  }
   686  
   687  func TestHandleTrade(t *testing.T) {
   688  	params := &RawParams{
   689  		PWArgs: []encode.PassBytes{encode.PassBytes("abc")}, // 0. AppPass
   690  		Args: []string{
   691  			"1.2.3.4:3000", // 0. DEX
   692  			"true",         // 1. IsLimit
   693  			"true",         // 2. Sell
   694  			"0",            // 3. Base
   695  			"42",           // 4. Quote
   696  			"1",            // 5. Qty
   697  			"1",            // 6. Rate
   698  			"true",         // 7. TifNow
   699  			`{"gas_price":"23","gas_limit":"120000"}`, // 8. Options
   700  		}}
   701  	tests := []struct {
   702  		name        string
   703  		params      *RawParams
   704  		tradeErr    error
   705  		wantErrCode int
   706  	}{{
   707  		name:        "ok",
   708  		params:      params,
   709  		wantErrCode: -1,
   710  	}, {
   711  		name:        "core.Trade error",
   712  		params:      params,
   713  		tradeErr:    errors.New("error"),
   714  		wantErrCode: msgjson.RPCTradeError,
   715  	}, {
   716  		name:        "bad params",
   717  		params:      &RawParams{},
   718  		wantErrCode: msgjson.RPCArgumentsError,
   719  	}}
   720  	for _, test := range tests {
   721  		tc := &TCore{order: new(core.Order), tradeErr: test.tradeErr}
   722  		r := &RPCServer{core: tc}
   723  		payload := handleTrade(r, test.params)
   724  		res := new(tradeResponse)
   725  		if err := verifyResponse(payload, res, test.wantErrCode); err != nil {
   726  			t.Fatal(err)
   727  		}
   728  	}
   729  }
   730  
   731  func TestHandleCancel(t *testing.T) {
   732  	params := &RawParams{
   733  		Args: []string{"fb94fe99e4e32200a341f0f1cb33f34a08ac23eedab636e8adb991fa76343e1e"},
   734  	}
   735  	tests := []struct {
   736  		name        string
   737  		params      *RawParams
   738  		cancelErr   error
   739  		wantErrCode int
   740  	}{{
   741  		name:        "ok",
   742  		params:      params,
   743  		wantErrCode: -1,
   744  	}, {
   745  		name:        "core.Cancel error",
   746  		params:      params,
   747  		cancelErr:   errors.New("error"),
   748  		wantErrCode: msgjson.RPCCancelError,
   749  	}, {
   750  		name:        "bad params",
   751  		params:      &RawParams{},
   752  		wantErrCode: msgjson.RPCArgumentsError,
   753  	}}
   754  	for _, test := range tests {
   755  		tc := &TCore{cancelErr: test.cancelErr}
   756  		r := &RPCServer{core: tc}
   757  		payload := handleCancel(r, test.params)
   758  		res := ""
   759  		if err := verifyResponse(payload, &res, test.wantErrCode); err != nil {
   760  			t.Fatal(err)
   761  		}
   762  	}
   763  }
   764  
   765  // tCoin satisfies the asset.Coin interface.
   766  type tCoin struct{}
   767  
   768  func (tCoin) ID() dex.Bytes {
   769  	return nil
   770  }
   771  func (tCoin) String() string {
   772  	return ""
   773  }
   774  func (tCoin) TxID() string {
   775  	return ""
   776  }
   777  func (tCoin) Value() uint64 {
   778  	return 0
   779  }
   780  func (tCoin) Confirmations(context.Context) (uint32, error) {
   781  	return 0, nil
   782  }
   783  
   784  func TestHandleSendAndWithdraw(t *testing.T) {
   785  	pw := encode.PassBytes("password123")
   786  	params := &RawParams{
   787  		PWArgs: []encode.PassBytes{pw},
   788  		Args: []string{
   789  			"42",
   790  			"1000",
   791  			"abc",
   792  		},
   793  	}
   794  
   795  	tests := []struct {
   796  		name        string
   797  		params      *RawParams
   798  		walletState *core.WalletState
   799  		coin        asset.Coin
   800  		sendErr     error
   801  		wantErrCode int
   802  	}{{
   803  		name:        "ok",
   804  		params:      params,
   805  		walletState: &core.WalletState{},
   806  		coin:        tCoin{},
   807  		wantErrCode: -1,
   808  	}, {
   809  		name:        "Send error",
   810  		params:      params,
   811  		walletState: &core.WalletState{},
   812  		coin:        tCoin{},
   813  		sendErr:     errors.New("error"),
   814  		wantErrCode: msgjson.RPCFundTransferError,
   815  	}, {
   816  		name:        "bad params",
   817  		params:      &RawParams{},
   818  		wantErrCode: msgjson.RPCArgumentsError,
   819  	}}
   820  
   821  	// Test handleWithdraw.
   822  	for _, test := range tests {
   823  		tc := &TCore{
   824  			walletState: test.walletState,
   825  			coin:        test.coin,
   826  			sendErr:     test.sendErr,
   827  		}
   828  		r := &RPCServer{core: tc}
   829  		payload := handleWithdraw(r, test.params)
   830  		res := ""
   831  		if err := verifyResponse(payload, &res, test.wantErrCode); err != nil {
   832  			t.Fatal(err)
   833  		}
   834  	}
   835  
   836  	// Test handleSend.
   837  	for _, test := range tests {
   838  		tc := &TCore{
   839  			walletState: test.walletState,
   840  			coin:        test.coin,
   841  			sendErr:     test.sendErr,
   842  		}
   843  		r := &RPCServer{core: tc}
   844  		payload := handleSend(r, test.params)
   845  		res := ""
   846  		if err := verifyResponse(payload, &res, test.wantErrCode); err != nil {
   847  			t.Fatal(err)
   848  		}
   849  	}
   850  }
   851  
   852  func TestHandleLogout(t *testing.T) {
   853  	tests := []struct {
   854  		name        string
   855  		logoutErr   error
   856  		wantErrCode int
   857  	}{{
   858  		name:        "ok",
   859  		wantErrCode: -1,
   860  	}, {
   861  		name:        "core.Logout error",
   862  		logoutErr:   errors.New("error"),
   863  		wantErrCode: msgjson.RPCLogoutError,
   864  	}}
   865  	for _, test := range tests {
   866  		tc := &TCore{
   867  			logoutErr: test.logoutErr,
   868  		}
   869  		r := &RPCServer{core: tc}
   870  		payload := handleLogout(r, nil)
   871  		res := ""
   872  		if err := verifyResponse(payload, &res, test.wantErrCode); err != nil {
   873  			t.Fatal(err)
   874  		}
   875  	}
   876  }
   877  
   878  func TestHandleOrderBook(t *testing.T) {
   879  	params := &RawParams{Args: []string{"dex", "42", "0"}}
   880  	paramsNOrders := &RawParams{Args: []string{"dex", "42", "0", "1"}}
   881  	tests := []struct {
   882  		name        string
   883  		params      *RawParams
   884  		book        *core.OrderBook
   885  		bookErr     error
   886  		wantErrCode int
   887  	}{{
   888  		name:        "ok no nOrders",
   889  		params:      params,
   890  		book:        new(core.OrderBook),
   891  		wantErrCode: -1,
   892  	}, {
   893  		name:        "ok with nOrders",
   894  		params:      paramsNOrders,
   895  		book:        new(core.OrderBook),
   896  		wantErrCode: -1,
   897  	}, {
   898  		name:        "core.Book error",
   899  		params:      params,
   900  		bookErr:     errors.New("error"),
   901  		wantErrCode: msgjson.RPCOrderBookError,
   902  	}, {
   903  		name:        "bad params",
   904  		params:      &RawParams{},
   905  		wantErrCode: msgjson.RPCArgumentsError,
   906  	}}
   907  	for _, test := range tests {
   908  		tc := &TCore{
   909  			book:    test.book,
   910  			bookErr: test.bookErr,
   911  		}
   912  		r := &RPCServer{core: tc}
   913  		payload := handleOrderBook(r, test.params)
   914  		res := new(core.OrderBook)
   915  		if err := verifyResponse(payload, res, test.wantErrCode); err != nil {
   916  			t.Fatal(err)
   917  		}
   918  	}
   919  }
   920  
   921  func TestTruncateOrderBook(t *testing.T) {
   922  	var lowRate uint64 = 1e8
   923  	var medRate uint64 = 1.5e8
   924  	var highRate uint64 = 2.0e8
   925  	lowRateOrder := &core.MiniOrder{MsgRate: lowRate}
   926  	medRateOrder := &core.MiniOrder{MsgRate: medRate}
   927  	highRateOrder := &core.MiniOrder{MsgRate: highRate}
   928  	book := &core.OrderBook{
   929  		Buys: []*core.MiniOrder{
   930  			highRateOrder,
   931  			medRateOrder,
   932  			lowRateOrder,
   933  		},
   934  		Sells: []*core.MiniOrder{
   935  			lowRateOrder,
   936  			medRateOrder,
   937  		},
   938  	}
   939  	truncateOrderBook(book, 4)
   940  	// no change
   941  	if len(book.Buys) != 3 && len(book.Sells) != 2 {
   942  		t.Fatal("no change was expected")
   943  	}
   944  	truncateOrderBook(book, 3)
   945  	// no change
   946  	if len(book.Buys) != 3 && len(book.Sells) != 2 {
   947  		t.Fatal("no change was expected")
   948  	}
   949  	truncateOrderBook(book, 2)
   950  	// buys truncated
   951  	if len(book.Buys) != 2 && len(book.Sells) != 2 {
   952  		t.Fatal("buys not truncated")
   953  	}
   954  	truncateOrderBook(book, 1)
   955  	// buys and sells truncated
   956  	if len(book.Buys) != 1 && len(book.Sells) != 1 {
   957  		t.Fatal("buys and sells not truncated")
   958  	}
   959  	if book.Buys[0].MsgRate != highRate {
   960  		t.Fatal("expected high rate order")
   961  	}
   962  	if book.Sells[0].MsgRate != lowRate {
   963  		t.Fatal("expected low rate order")
   964  	}
   965  }
   966  
   967  func TestHandleMyOrders(t *testing.T) {
   968  	var exchangesIn map[string]*core.Exchange
   969  	if err := json.Unmarshal([]byte(exchangeIn), &exchangesIn); err != nil {
   970  		panic(err)
   971  	}
   972  	paramsWithArgs := func(ss ...string) *RawParams {
   973  		args := []string{}
   974  		args = append(args, ss...)
   975  		return &RawParams{Args: args}
   976  	}
   977  	tests := []struct {
   978  		name        string
   979  		params      *RawParams
   980  		wantErrCode int
   981  	}{{
   982  		name:        "ok no params",
   983  		params:      paramsWithArgs(),
   984  		wantErrCode: -1,
   985  	}, {
   986  		name:        "ok with host param",
   987  		params:      paramsWithArgs("127.0.0.1:7232"),
   988  		wantErrCode: -1,
   989  	}, {
   990  		name:        "ok with host and baseID/quoteID params",
   991  		params:      paramsWithArgs("127.0.0.1:7232", "42", "0"),
   992  		wantErrCode: -1,
   993  	}, {
   994  		name:        "ok with no host and baseID/quoteID params",
   995  		params:      paramsWithArgs("", "42", "0"),
   996  		wantErrCode: -1,
   997  	}, {
   998  		name:        "bad params",
   999  		params:      paramsWithArgs("", "42"), // missing quote ID
  1000  		wantErrCode: msgjson.RPCArgumentsError,
  1001  	}}
  1002  	for _, test := range tests {
  1003  		tc := &TCore{exchanges: exchangesIn}
  1004  		r := &RPCServer{core: tc}
  1005  		payload := handleMyOrders(r, test.params)
  1006  		res := new(myOrdersResponse)
  1007  		if err := verifyResponse(payload, res, test.wantErrCode); err != nil {
  1008  			t.Fatal(err)
  1009  		}
  1010  	}
  1011  }
  1012  
  1013  func TestParseCoreOrder(t *testing.T) {
  1014  	co := `{
  1015      "canceled": false,
  1016      "cancelling": false,
  1017      "epoch": 159650082,
  1018      "filled": 300000000,
  1019      "host": "127.0.0.1:7232",
  1020      "id": "ca0097c87dbf01169d76b6f2a318f88fe0ea678df3139f09d756d7d3e2c602dd",
  1021      "market": "dcr_btc",
  1022      "matches": [
  1023        {
  1024          "matchID": "992f15e89bbd670663b690b4da4a859609d83866e200f3c4cd5c916442b8ea46",
  1025          "qty": 100000000,
  1026          "rate": 200000000,
  1027          "side": 0,
  1028          "status": 4
  1029        },
  1030        {
  1031          "matchID": "69d7453d8ad3b52851c2c9925499a1b158301e8a08da594428ef0ad4cd6fd3a5",
  1032          "qty": 200000000,
  1033          "rate": 200000000,
  1034          "side": 0,
  1035          "status": 1
  1036        }
  1037      ],
  1038      "qty": 400000000,
  1039      "rate": 200000000,
  1040      "sell": false,
  1041      "sig": "30450221008eaf7fa3e5b4374800d11e50af419d3fa7c75362dce136df98a25eccc84e61380220458132451b40aa6951ab5b61d6b55c478fd9535c3d24fd4957070c7879e465ff",
  1042      "stamp": 1596500829705,
  1043      "status": 2,
  1044      "tif": 1,
  1045      "type": 1
  1046    }`
  1047  
  1048  	mo := `{
  1049      "host": "127.0.0.1:7232",
  1050      "marketName": "dcr_btc",
  1051      "baseID": 42,
  1052      "quoteID": 0,
  1053      "id": "ca0097c87dbf01169d76b6f2a318f88fe0ea678df3139f09d756d7d3e2c602dd",
  1054      "type": "limit",
  1055      "sell": false,
  1056      "stamp": 1596500829705,
  1057      "age": "2.664424s",
  1058      "rate": 200000000,
  1059      "quantity": 400000000,
  1060      "filled": 300000000,
  1061      "settled": 100000000,
  1062      "status": "booked",
  1063      "tif": "standing",
  1064      "matches": [
  1065        {
  1066          "matchID": "992f15e89bbd670663b690b4da4a859609d83866e200f3c4cd5c916442b8ea46",
  1067          "status": "MatchComplete",
  1068          "revoked": false,
  1069          "rate": 200000000,
  1070          "qty": 100000000,
  1071          "side": "Maker",
  1072          "feeRate": 0,
  1073          "swap": "",
  1074          "counterSwap":"",
  1075          "redeem": "",
  1076          "counterRedeem": "",
  1077          "refund": "",
  1078          "stamp": 0,
  1079          "isCancel": false
  1080       	},
  1081        {
  1082          "matchID": "69d7453d8ad3b52851c2c9925499a1b158301e8a08da594428ef0ad4cd6fd3a5",
  1083          "status": "MakerSwapCast",
  1084          "revoked": false,
  1085          "rate": 200000000,
  1086          "qty": 200000000,
  1087          "side": "Maker",
  1088          "feeRate": 0,
  1089          "swap": "",
  1090          "counterSwap": "",
  1091          "redeem": "",
  1092          "counterRedeem": "",
  1093          "refund": "",
  1094          "tamp": 0,
  1095          "isCancel": false
  1096        }
  1097      ]
  1098    }`
  1099  	coreOrder := new(core.Order)
  1100  	if err := json.Unmarshal([]byte(co), coreOrder); err != nil {
  1101  		panic(err)
  1102  	}
  1103  	myOrder := new(myOrder)
  1104  	if err := json.Unmarshal([]byte(mo), myOrder); err != nil {
  1105  		panic(err)
  1106  	}
  1107  
  1108  	res := parseCoreOrder(coreOrder, 42, 0)
  1109  	// Age will differ as it is based on the current time.
  1110  	myOrder.Age = res.Age
  1111  	if !reflect.DeepEqual(myOrder, res) {
  1112  		t.Fatalf("expected %v but got %v", spew.Sdump(myOrder), spew.Sdump(res))
  1113  	}
  1114  }
  1115  
  1116  func TestHandleAppSeed(t *testing.T) {
  1117  	params := &RawParams{
  1118  		PWArgs: []encode.PassBytes{encode.PassBytes("abc")},
  1119  	}
  1120  	tests := []struct {
  1121  		name          string
  1122  		params        *RawParams
  1123  		exportSeedErr error
  1124  		wantErrCode   int
  1125  	}{{
  1126  		name:        "ok",
  1127  		params:      params,
  1128  		wantErrCode: -1,
  1129  	}, {
  1130  		name:          "core.ExportSeed error",
  1131  		params:        params,
  1132  		exportSeedErr: errors.New("error"),
  1133  		wantErrCode:   msgjson.RPCExportSeedError,
  1134  	}, {
  1135  		name:        "bad params",
  1136  		params:      &RawParams{},
  1137  		wantErrCode: msgjson.RPCArgumentsError,
  1138  	}}
  1139  	for _, test := range tests {
  1140  		tc := &TCore{exportSeed: "seed words here", exportSeedErr: test.exportSeedErr}
  1141  		r := &RPCServer{core: tc}
  1142  		payload := handleAppSeed(r, test.params)
  1143  		res := ""
  1144  		if err := verifyResponse(payload, &res, test.wantErrCode); err != nil {
  1145  			t.Fatal(err)
  1146  		}
  1147  		res = strings.ToLower(res)
  1148  		if test.wantErrCode == -1 && res != "seed words here" {
  1149  			t.Fatalf("expected ff but got %v", res)
  1150  		}
  1151  
  1152  	}
  1153  }
  1154  
  1155  func TestHandleDiscoverAcct(t *testing.T) {
  1156  	pw := encode.PassBytes("password123")
  1157  	params := &RawParams{
  1158  		PWArgs: []encode.PassBytes{pw},
  1159  		Args: []string{
  1160  			"dex:1234",
  1161  			"cert",
  1162  		},
  1163  	}
  1164  	tests := []struct {
  1165  		name            string
  1166  		params          *RawParams
  1167  		discoverAcctErr error
  1168  		wantErrCode     int
  1169  	}{{
  1170  		name:        "ok",
  1171  		params:      params,
  1172  		wantErrCode: -1,
  1173  	}, {
  1174  		name:            "discover account error",
  1175  		params:          params,
  1176  		discoverAcctErr: errors.New(""),
  1177  		wantErrCode:     msgjson.RPCDiscoverAcctError,
  1178  	}, {
  1179  		name:        "bad params",
  1180  		params:      &RawParams{},
  1181  		wantErrCode: msgjson.RPCArgumentsError,
  1182  	}}
  1183  	for _, test := range tests {
  1184  		tc := &TCore{
  1185  			discoverAcctErr: test.discoverAcctErr,
  1186  		}
  1187  		r := &RPCServer{core: tc}
  1188  		payload := handleDiscoverAcct(r, test.params)
  1189  		res := new(bool)
  1190  		if err := verifyResponse(payload, res, test.wantErrCode); err != nil {
  1191  			t.Fatal(err)
  1192  		}
  1193  	}
  1194  }
  1195  
  1196  func TestDeleteRecords(t *testing.T) {
  1197  	params := &RawParams{
  1198  		Args: []string{
  1199  			"123",
  1200  		},
  1201  	}
  1202  	tests := []struct {
  1203  		name                     string
  1204  		params                   *RawParams
  1205  		deleteArchivedRecordsErr error
  1206  		wantErrCode              int
  1207  	}{{
  1208  		name:        "ok",
  1209  		params:      params,
  1210  		wantErrCode: -1,
  1211  	}, {
  1212  		name:                     "delete archived records error",
  1213  		params:                   params,
  1214  		deleteArchivedRecordsErr: errors.New(""),
  1215  		wantErrCode:              msgjson.RPCDeleteArchivedRecordsError,
  1216  	}, {
  1217  		name: "bad params",
  1218  		params: &RawParams{
  1219  			Args: []string{
  1220  				"abc",
  1221  			},
  1222  		},
  1223  		wantErrCode: msgjson.RPCArgumentsError,
  1224  	}}
  1225  	for _, test := range tests {
  1226  		tc := &TCore{
  1227  			deleteArchivedRecordsErr: test.deleteArchivedRecordsErr,
  1228  		}
  1229  		tc.archivedRecords = 10
  1230  		r := &RPCServer{core: tc}
  1231  		payload := handleDeleteArchivedRecords(r, test.params)
  1232  		res := ""
  1233  		if err := verifyResponse(payload, &res, test.wantErrCode); err != nil {
  1234  			t.Fatal(err)
  1235  		}
  1236  		if test.wantErrCode < 0 && res == "" {
  1237  			t.Fatal("expected a non empty response for success")
  1238  		}
  1239  	}
  1240  }
  1241  
  1242  func TestHandleSetVSP(t *testing.T) {
  1243  	params := &RawParams{
  1244  		Args: []string{
  1245  			"42",
  1246  			"url.com",
  1247  		},
  1248  	}
  1249  	tests := []struct {
  1250  		name        string
  1251  		params      *RawParams
  1252  		setVSPErr   error
  1253  		wantErrCode int
  1254  	}{{
  1255  		name:        "ok",
  1256  		params:      params,
  1257  		wantErrCode: -1,
  1258  	}, {
  1259  		name:        "core.SetVSP error",
  1260  		params:      params,
  1261  		setVSPErr:   errors.New("error"),
  1262  		wantErrCode: msgjson.RPCSetVSPError,
  1263  	}, {
  1264  		name:        "bad params",
  1265  		params:      &RawParams{},
  1266  		wantErrCode: msgjson.RPCArgumentsError,
  1267  	}}
  1268  	for _, test := range tests {
  1269  		tc := &TCore{setVSPErr: test.setVSPErr}
  1270  		r := &RPCServer{core: tc}
  1271  		payload := handleSetVSP(r, test.params)
  1272  		res := ""
  1273  		if err := verifyResponse(payload, &res, test.wantErrCode); err != nil {
  1274  			t.Fatal(err)
  1275  		}
  1276  	}
  1277  }
  1278  
  1279  func TestPurchaseTickets(t *testing.T) {
  1280  	pw := encode.PassBytes("password123")
  1281  	params := &RawParams{
  1282  		PWArgs: []encode.PassBytes{pw},
  1283  		Args: []string{
  1284  			"42",
  1285  			"2",
  1286  		},
  1287  	}
  1288  	tc := &TCore{}
  1289  	r := &RPCServer{core: tc}
  1290  	payload := handlePurchaseTickets(r, params)
  1291  	var res bool
  1292  	err := verifyResponse(payload, &res, -1)
  1293  	if err != nil {
  1294  		t.Fatal(err)
  1295  	}
  1296  	if err = verifyResponse(payload, &res, -1); err != nil {
  1297  		t.Fatal(err)
  1298  	}
  1299  	if res != true {
  1300  		t.Fatal("result is false")
  1301  	}
  1302  
  1303  	tc.purchaseTicketsErr = errors.New("test error")
  1304  	payload = handlePurchaseTickets(r, params)
  1305  	if err = verifyResponse(payload, &res, msgjson.RPCPurchaseTicketsError); err != nil {
  1306  		t.Fatal(err)
  1307  	}
  1308  }
  1309  
  1310  func TestHandleStakeStatus(t *testing.T) {
  1311  	params := &RawParams{
  1312  		Args: []string{
  1313  			"42",
  1314  		},
  1315  	}
  1316  	tests := []struct {
  1317  		name           string
  1318  		params         *RawParams
  1319  		stakeStatusErr error
  1320  		wantErrCode    int
  1321  	}{{
  1322  		name:        "ok",
  1323  		params:      params,
  1324  		wantErrCode: -1,
  1325  	}, {
  1326  		name:           "core.StakeStatus error",
  1327  		params:         params,
  1328  		stakeStatusErr: errors.New("error"),
  1329  		wantErrCode:    msgjson.RPCStakeStatusError,
  1330  	}, {
  1331  		name:        "bad params",
  1332  		params:      &RawParams{},
  1333  		wantErrCode: msgjson.RPCArgumentsError,
  1334  	}}
  1335  	for _, test := range tests {
  1336  		tc := &TCore{stakeStatusErr: test.stakeStatusErr}
  1337  		r := &RPCServer{core: tc}
  1338  		payload := handleStakeStatus(r, test.params)
  1339  		res := new(asset.TicketStakingStatus)
  1340  		if err := verifyResponse(payload, &res, test.wantErrCode); err != nil {
  1341  			t.Fatal(err)
  1342  		}
  1343  	}
  1344  }
  1345  
  1346  func TestHandleSetVotingPreferences(t *testing.T) {
  1347  	params := &RawParams{
  1348  		Args: []string{
  1349  			"42",
  1350  		},
  1351  	}
  1352  	tests := []struct {
  1353  		name             string
  1354  		params           *RawParams
  1355  		setVotingPrefErr error
  1356  		wantErrCode      int
  1357  	}{{
  1358  		name:        "ok",
  1359  		params:      params,
  1360  		wantErrCode: -1,
  1361  	}, {
  1362  		name:             "core.SetVotingPreferences error",
  1363  		params:           params,
  1364  		setVotingPrefErr: errors.New("error"),
  1365  		wantErrCode:      msgjson.RPCSetVotingPreferencesError,
  1366  	}, {
  1367  		name: "bad params",
  1368  		params: &RawParams{
  1369  			Args: []string{
  1370  				"asdf",
  1371  			},
  1372  		},
  1373  		wantErrCode: msgjson.RPCArgumentsError,
  1374  	}}
  1375  	for _, test := range tests {
  1376  		tc := &TCore{setVotingPrefErr: test.setVotingPrefErr}
  1377  		r := &RPCServer{core: tc}
  1378  		payload := handleSetVotingPreferences(r, test.params)
  1379  		res := ""
  1380  		if err := verifyResponse(payload, &res, test.wantErrCode); err != nil {
  1381  			t.Fatal(err)
  1382  		}
  1383  	}
  1384  }