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 }