mirror of
https://github.com/d0zingcat/gocryptotrader.git
synced 2026-05-13 23:16:45 +00:00
exchanges/qa: Add exchange wrapper testing suite (#1159)
* initial concept of a nice validation tester for exchanges * adds some datahandler design * expand testing * more tests and fixes * minor end of day fix for bithumb * fixes implementation issues * more test coverage and improvements, but not sure if i should continue * fix more wrapper implementations * adds error type, more fixes * changes signature, fixes implementations * fixes more wrapper implementations * one more bit * more cleanup * WOW things work? * lintle 1/1337 * mini bump * fixes all linting * neaten * GetOrderInfo+ asset pair fixes+improvements * adds new websocket test * expand ws testing * fix bug, expand tests, improve implementation * code coverage of a lot of new codes * fixes everything * reverts accidental changes * minor fixes from reviewing code * removes Bitfinex cancelBatchOrder implementation * fixes dumb baby typo for babies * mini nit fixes * so many nits to address * addresses all the nits * Titlecase * switcheroo * removes websocket testing for now * fix appveyor, minor test fix * fixes typo, re-kindles killed kode * skip binance wrapper tests when running CI * expired context, huobi okx fixes * kodespull * fix ordering * time fix because why not * fix exmo, others * hopefully this fixes all of my life's problems * last thing today * huobi, more like hypotrophy * golangci-lint, more like mypooroldknee-splint * fix huobi times by removing them * should fix okx currency issues * blocks the application * adds last little contingency for pairs * addresses most nits and new problems * lovely fixed before seeing why okx sucks * fixes issues with okx websocket * the classic receieieivaier * lintle * adds test and fixes existing tests * expands error handling messages during setup * fixes dumb okx bugs introduced * quick fix for lint and exmo * fixes nixes * fix exmo deposit issue * lint * fixes issue with extra asset runs missing * fix surprise race * all the lint and merge fixes * fixes surprise bugs in OKx * fixes issues with times and chains * fixing all the merge stuff * merge fix * rm logs and a panic potential * lovely lint lament * an easy demonstration of scenario, but not of initial purpose * put it in the bin * Revert "put it in the bin" This reverts commit 15c6490f713233d43f10957367fcbf18e3818bdd. * re-add after immediate error popup * fix mini poor test design * okx okay * merge fixes * fixes issues discovered in lovely test * I FORGOT TO COMMIT THIS * nit fixaroonaboo * forgoetten test fix * revert old okx asset intrument work * fixes * revert problems I didnt understand. update bybit * fix merge bugs * test cleanup * further improvements * reshuffle and lint * rm redundant CI_TEST by rm the CI_TEST field that is redundant * path fix * move to its own section, dont run on 32 bit + appveyor * lint * fix lbank * address nits * let it rip * fix failing test time range * niteroo boogaloo * mod tidy, use common.SimpleTimeFormat
This commit is contained in:
@@ -690,7 +690,7 @@ func (m *apiServerManager) WebsocketClientHandler(w http.ResponseWriter, r *http
|
||||
func wsAuth(client *websocketClient, data interface{}) error {
|
||||
d, ok := data.([]byte)
|
||||
if !ok {
|
||||
return errors.New("unable to type assert data")
|
||||
return common.GetTypeAssertError("[]byte", data)
|
||||
}
|
||||
|
||||
wsResp := WebsocketEventResponse{
|
||||
@@ -753,7 +753,7 @@ func wsGetConfig(client *websocketClient, _ interface{}) error {
|
||||
func wsSaveConfig(client *websocketClient, data interface{}) error {
|
||||
d, ok := data.([]byte)
|
||||
if !ok {
|
||||
return errors.New("unable to type assert data")
|
||||
return common.GetTypeAssertError("[]byte", data)
|
||||
}
|
||||
|
||||
wsResp := WebsocketEventResponse{
|
||||
@@ -814,7 +814,7 @@ func wsGetTickers(client *websocketClient, _ interface{}) error {
|
||||
func wsGetTicker(client *websocketClient, data interface{}) error {
|
||||
d, ok := data.([]byte)
|
||||
if !ok {
|
||||
return errors.New("unable to type assert data")
|
||||
return common.GetTypeAssertError("[]byte", data)
|
||||
}
|
||||
|
||||
wsResp := WebsocketEventResponse{
|
||||
@@ -874,7 +874,7 @@ func wsGetOrderbooks(client *websocketClient, _ interface{}) error {
|
||||
func wsGetOrderbook(client *websocketClient, data interface{}) error {
|
||||
d, ok := data.([]byte)
|
||||
if !ok {
|
||||
return errors.New("unable to type assert data")
|
||||
return common.GetTypeAssertError("[]byte", data)
|
||||
}
|
||||
|
||||
wsResp := WebsocketEventResponse{
|
||||
|
||||
@@ -115,7 +115,7 @@ func (c *CurrencyStateManager) monitor() {
|
||||
wg.Add(1)
|
||||
go c.update(exchs[x], &wg, exchs[x].GetAssetTypes(true))
|
||||
}
|
||||
wg.Wait() // This causes some variability in the timer due to
|
||||
wg.Wait() // This causes some variability in the timer due to the
|
||||
// longest length of request time. Can do time.Ticker but don't
|
||||
// want routines to stack behind, this is more uniform.
|
||||
timer.Reset(c.sleep)
|
||||
|
||||
@@ -1456,8 +1456,7 @@ func (d dataHistoryJobService) SetRelationshipByNickname(prereq, _ string, statu
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d dataHistoryJobService) GetByNickName(nickname string) (*datahistoryjob.DataHistoryJob, error) {
|
||||
d.job.Nickname = nickname
|
||||
func (d dataHistoryJobService) GetByNickName(_ string) (*datahistoryjob.DataHistoryJob, error) {
|
||||
return d.job, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -16,6 +16,7 @@ import (
|
||||
"github.com/thrasher-corp/gocryptotrader/common"
|
||||
"github.com/thrasher-corp/gocryptotrader/config"
|
||||
"github.com/thrasher-corp/gocryptotrader/currency"
|
||||
"github.com/thrasher-corp/gocryptotrader/database"
|
||||
"github.com/thrasher-corp/gocryptotrader/dispatch"
|
||||
exchange "github.com/thrasher-corp/gocryptotrader/exchanges"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/alert"
|
||||
@@ -307,7 +308,7 @@ func (bot *Engine) Start() error {
|
||||
gctlog.Errorf(gctlog.Global, "Database manager unable to setup: %v", err)
|
||||
} else {
|
||||
err = bot.DatabaseManager.Start(&bot.ServicesWG)
|
||||
if err != nil {
|
||||
if err != nil && !errors.Is(err, database.ErrDatabaseSupportDisabled) {
|
||||
gctlog.Errorf(gctlog.Global, "Database manager unable to start: %v", err)
|
||||
}
|
||||
}
|
||||
@@ -853,17 +854,11 @@ func (bot *Engine) LoadExchange(name string, wg *sync.WaitGroup) error {
|
||||
}
|
||||
}
|
||||
|
||||
if wg != nil {
|
||||
return exch.Start(context.TODO(), wg)
|
||||
if wg == nil {
|
||||
wg = &sync.WaitGroup{}
|
||||
defer wg.Wait()
|
||||
}
|
||||
|
||||
tempWG := sync.WaitGroup{}
|
||||
err = exch.Start(context.TODO(), &tempWG)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
tempWG.Wait()
|
||||
return nil
|
||||
return exch.Start(context.TODO(), wg)
|
||||
}
|
||||
|
||||
func (bot *Engine) dryRunParamInteraction(param string) {
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
"errors"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
@@ -12,6 +13,16 @@ import (
|
||||
exchange "github.com/thrasher-corp/gocryptotrader/exchanges"
|
||||
)
|
||||
|
||||
// blockedCIExchanges are exchanges that are not able to be tested on CI
|
||||
var blockedCIExchanges = []string{
|
||||
"binance", // binance API is banned from executing within the US where github Actions is ran
|
||||
}
|
||||
|
||||
func isCITest() bool {
|
||||
ci := os.Getenv("CI")
|
||||
return ci == "true" /* github actions */ || ci == "True" /* appveyor */
|
||||
}
|
||||
|
||||
func TestLoadConfigWithSettings(t *testing.T) {
|
||||
empty := ""
|
||||
somePath := "somePath"
|
||||
@@ -342,41 +353,40 @@ func TestSettingsPrint(t *testing.T) {
|
||||
s.PrintLoadedSettings()
|
||||
}
|
||||
|
||||
var unsupportedDefaultConfigExchanges = []string{
|
||||
"okcoin international", // due to unsupported API
|
||||
"itbit", // due to unsupported API
|
||||
}
|
||||
|
||||
func TestGetDefaultConfigurations(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
man := NewExchangeManager()
|
||||
for x := range exchange.Exchanges {
|
||||
target := exchange.Exchanges[x]
|
||||
t.Run(target, func(t *testing.T) {
|
||||
em := NewExchangeManager()
|
||||
for i := range exchange.Exchanges {
|
||||
name := strings.ToLower(exchange.Exchanges[i])
|
||||
t.Run(name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
exch, err := man.NewExchangeByName(target)
|
||||
exch, err := em.NewExchangeByName(name)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if isCITest() && common.StringDataContains(blockedCIExchanges, target) {
|
||||
t.Skipf("skipping %s due to CI test restrictions", target)
|
||||
if isCITest() && common.StringDataContains(blockedCIExchanges, name) {
|
||||
t.Skipf("skipping %s due to CI test restrictions", name)
|
||||
}
|
||||
|
||||
cfg, err := exch.GetDefaultConfig(context.Background())
|
||||
if common.StringDataContains(unsupportedDefaultConfigExchanges, name) {
|
||||
t.Skipf("skipping %s unsupported", name)
|
||||
}
|
||||
|
||||
defaultCfg, err := exch.GetDefaultConfig(context.Background())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
// Use Error instead of fatal to allow all issues to arise
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
if cfg == nil {
|
||||
t.Fatal("expected config")
|
||||
if defaultCfg == nil {
|
||||
t.Error("expected config")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func isCITest() bool {
|
||||
ci := os.Getenv("CI")
|
||||
return ci == "true" /* github actions */ || ci == "True" /* appveyor */
|
||||
}
|
||||
|
||||
// blockedCIExchanges are exchanges that are not able to be tested on CI
|
||||
var blockedCIExchanges = []string{
|
||||
"binance", // binance API is banned from executing within the US where github Actions is ran
|
||||
}
|
||||
|
||||
@@ -281,6 +281,14 @@ func TestCheckEventCondition(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
exch.SetDefaults()
|
||||
conf, err := exch.GetDefaultConfig(context.Background())
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
err = exch.Setup(conf)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
err = em.Add(exch)
|
||||
if !errors.Is(err, nil) {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", err, nil)
|
||||
|
||||
@@ -159,7 +159,8 @@ func TestNewExchangeByName(t *testing.T) {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", err, ErrExchangeNameIsEmpty)
|
||||
}
|
||||
|
||||
exchanges := []string{"binanceus", "binance", "bitfinex", "bitflyer", "bithumb", "bitmex", "bitstamp", "bittrex", "btc markets", "btse", "bybit", "coinut", "exmo", "coinbasepro", "gateio", "gemini", "hitbtc", "huobi", "itbit", "kraken", "lbank", "okcoin international", "okx", "poloniex", "yobit", "zb", "fake"}
|
||||
exchanges := exchange.Exchanges
|
||||
exchanges = append(exchanges, "fake")
|
||||
for i := range exchanges {
|
||||
var exch exchange.IBotExchange
|
||||
exch, err = m.NewExchangeByName(exchanges[i])
|
||||
|
||||
@@ -17,6 +17,7 @@ import (
|
||||
"github.com/thrasher-corp/gocryptotrader/currency"
|
||||
exchange "github.com/thrasher-corp/gocryptotrader/exchanges"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/currencystate"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/order"
|
||||
"github.com/thrasher-corp/gocryptotrader/log"
|
||||
)
|
||||
@@ -192,8 +193,7 @@ func (m *OrderManager) Cancel(ctx context.Context, cancel *order.Cancel) error {
|
||||
}
|
||||
|
||||
if cancel.AssetType.String() != "" && !exch.GetAssetTypes(false).Contains(cancel.AssetType) {
|
||||
err = errors.New("order asset type not supported by exchange")
|
||||
return err
|
||||
return fmt.Errorf("%w %v", asset.ErrNotSupported, cancel.AssetType)
|
||||
}
|
||||
|
||||
log.Debugf(log.OrderMgr, "Cancelling order ID %v [%+v]",
|
||||
@@ -329,7 +329,7 @@ func (m *OrderManager) GetOrderInfo(ctx context.Context, exchangeName, orderID s
|
||||
return order.Detail{}, err
|
||||
}
|
||||
|
||||
upsertResponse, err := m.orderStore.upsert(&result)
|
||||
upsertResponse, err := m.orderStore.upsert(result)
|
||||
if err != nil {
|
||||
return order.Detail{}, err
|
||||
}
|
||||
@@ -468,7 +468,7 @@ func (m *OrderManager) Submit(ctx context.Context, newOrder *order.Submit) (*Ord
|
||||
newOrder.Price,
|
||||
newOrder.Amount,
|
||||
newOrder.Type)
|
||||
if err != nil {
|
||||
if err != nil && errors.Is(err, currencystate.ErrCurrencyStateNotFound) {
|
||||
return nil, fmt.Errorf("order manager: exchange %s unable to place order: %w",
|
||||
newOrder.Exchange,
|
||||
err)
|
||||
@@ -663,7 +663,7 @@ func (m *OrderManager) processOrders() {
|
||||
orders := m.orderStore.getActiveOrders(filter)
|
||||
order.FilterOrdersByPairs(&orders, pairs)
|
||||
var result []order.Detail
|
||||
result, err = exchanges[x].GetActiveOrders(context.TODO(), &order.GetOrdersRequest{
|
||||
result, err = exchanges[x].GetActiveOrders(context.TODO(), &order.MultiOrderRequest{
|
||||
Side: order.AnySide,
|
||||
Type: order.AnyType,
|
||||
Pairs: pairs,
|
||||
@@ -832,7 +832,7 @@ func (m *OrderManager) FetchAndUpdateExchangeOrder(exch exchange.IBotExchange, o
|
||||
return err
|
||||
}
|
||||
fetchedOrder.LastUpdated = time.Now()
|
||||
_, err = m.UpsertOrder(&fetchedOrder)
|
||||
_, err = m.UpsertOrder(fetchedOrder)
|
||||
return err
|
||||
}
|
||||
|
||||
|
||||
@@ -60,12 +60,12 @@ func (f omfExchange) FetchTicker(_ context.Context, p currency.Pair, a asset.Ite
|
||||
|
||||
// GetOrderInfo overrides testExchange's get order function
|
||||
// to do the bare minimum required with no API calls or credentials required
|
||||
func (f omfExchange) GetOrderInfo(_ context.Context, orderID string, pair currency.Pair, assetType asset.Item) (order.Detail, error) {
|
||||
func (f omfExchange) GetOrderInfo(_ context.Context, orderID string, pair currency.Pair, assetType asset.Item) (*order.Detail, error) {
|
||||
switch orderID {
|
||||
case "":
|
||||
return order.Detail{}, errors.New("")
|
||||
return nil, errors.New("")
|
||||
case "Order1-unknown-to-active":
|
||||
return order.Detail{
|
||||
return &order.Detail{
|
||||
Exchange: testExchange,
|
||||
Pair: currency.Pair{Base: currency.BTC, Quote: currency.USD},
|
||||
AssetType: asset.Spot,
|
||||
@@ -76,7 +76,7 @@ func (f omfExchange) GetOrderInfo(_ context.Context, orderID string, pair curren
|
||||
OrderID: "Order1-unknown-to-active",
|
||||
}, nil
|
||||
case "Order2-active-to-inactive":
|
||||
return order.Detail{
|
||||
return &order.Detail{
|
||||
Exchange: testExchange,
|
||||
Pair: currency.Pair{Base: currency.BTC, Quote: currency.USD},
|
||||
AssetType: asset.Spot,
|
||||
@@ -88,7 +88,7 @@ func (f omfExchange) GetOrderInfo(_ context.Context, orderID string, pair curren
|
||||
}, nil
|
||||
}
|
||||
|
||||
return order.Detail{
|
||||
return &order.Detail{
|
||||
Exchange: testExchange,
|
||||
OrderID: orderID,
|
||||
Pair: pair,
|
||||
@@ -98,7 +98,7 @@ func (f omfExchange) GetOrderInfo(_ context.Context, orderID string, pair curren
|
||||
}
|
||||
|
||||
// GetActiveOrders overrides the function used by processOrders to return 1 active order
|
||||
func (f omfExchange) GetActiveOrders(_ context.Context, _ *order.GetOrdersRequest) (order.FilteredOrders, error) {
|
||||
func (f omfExchange) GetActiveOrders(_ context.Context, _ *order.MultiOrderRequest) (order.FilteredOrders, error) {
|
||||
return []order.Detail{{
|
||||
Exchange: testExchange,
|
||||
Pair: currency.Pair{Base: currency.BTC, Quote: currency.USD},
|
||||
|
||||
@@ -721,7 +721,7 @@ func (s *RPCServer) GetAccountInfoStream(r *gctrpc.GetAccountInfoRequest, stream
|
||||
|
||||
holdings, ok := data.(*account.Holdings)
|
||||
if !ok {
|
||||
return common.GetAssertError("*account.Holdings", data)
|
||||
return common.GetTypeAssertError("*account.Holdings", data)
|
||||
}
|
||||
|
||||
accounts := make([]*gctrpc.Account, len(holdings.Accounts))
|
||||
@@ -957,7 +957,7 @@ func (s *RPCServer) GetOrders(ctx context.Context, r *gctrpc.GetOrdersRequest) (
|
||||
return nil, err
|
||||
}
|
||||
|
||||
request := &order.GetOrdersRequest{
|
||||
request := &order.MultiOrderRequest{
|
||||
Pairs: []currency.Pair{cp},
|
||||
AssetType: a,
|
||||
Type: order.AnyType,
|
||||
@@ -1983,7 +1983,7 @@ func (s *RPCServer) GetExchangePairs(_ context.Context, r *gctrpc.GetExchangePai
|
||||
return nil, err
|
||||
}
|
||||
if !assetTypes.Contains(a) {
|
||||
return nil, fmt.Errorf("specified asset %s is not supported by exchange", a)
|
||||
return nil, fmt.Errorf("%w %v", asset.ErrNotSupported, a)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2195,7 +2195,7 @@ func (s *RPCServer) GetExchangeOrderbookStream(r *gctrpc.GetExchangeOrderbookStr
|
||||
|
||||
d, ok := data.(orderbook.Outbound)
|
||||
if !ok {
|
||||
return common.GetAssertError("orderbook.Outbound", data)
|
||||
return common.GetTypeAssertError("orderbook.Outbound", data)
|
||||
}
|
||||
|
||||
resp := &gctrpc.OrderbookResponse{}
|
||||
@@ -2280,7 +2280,7 @@ func (s *RPCServer) GetTickerStream(r *gctrpc.GetTickerStreamRequest, stream gct
|
||||
|
||||
t, ok := data.(*ticker.Price)
|
||||
if !ok {
|
||||
return common.GetAssertError("*ticker.Price", data)
|
||||
return common.GetTypeAssertError("*ticker.Price", data)
|
||||
}
|
||||
|
||||
err := stream.Send(&gctrpc.TickerResponse{
|
||||
@@ -2333,7 +2333,7 @@ func (s *RPCServer) GetExchangeTickerStream(r *gctrpc.GetExchangeTickerStreamReq
|
||||
|
||||
t, ok := data.(*ticker.Price)
|
||||
if !ok {
|
||||
return common.GetAssertError("*ticker.Price", data)
|
||||
return common.GetTypeAssertError("*ticker.Price", data)
|
||||
}
|
||||
|
||||
err := stream.Send(&gctrpc.TickerResponse{
|
||||
@@ -2575,7 +2575,7 @@ func (s *RPCServer) GCTScriptStatus(_ context.Context, _ *gctrpc.GCTScriptStatus
|
||||
gctscript.AllVMSync.Range(func(k, v interface{}) bool {
|
||||
vm, ok := v.(*gctscript.VM)
|
||||
if !ok {
|
||||
log.Errorf(log.GRPCSys, "%v", common.GetAssertError("*gctscript.VM", v))
|
||||
log.Errorf(log.GRPCSys, "%v", common.GetTypeAssertError("*gctscript.VM", v))
|
||||
return false
|
||||
}
|
||||
resp.Scripts = append(resp.Scripts, &gctrpc.GCTScript{
|
||||
@@ -2609,7 +2609,7 @@ func (s *RPCServer) GCTScriptQuery(_ context.Context, r *gctrpc.GCTScriptQueryRe
|
||||
|
||||
vm, ok := v.(*gctscript.VM)
|
||||
if !ok {
|
||||
return nil, errors.New("unable to type assert gctscript.VM")
|
||||
return nil, common.GetTypeAssertError("*gctscript.VM", v)
|
||||
}
|
||||
resp := &gctrpc.GCTScriptQueryResponse{
|
||||
Status: MsgStatusOK,
|
||||
@@ -2677,7 +2677,7 @@ func (s *RPCServer) GCTScriptStop(_ context.Context, r *gctrpc.GCTScriptStopRequ
|
||||
|
||||
vm, ok := v.(*gctscript.VM)
|
||||
if !ok {
|
||||
return nil, errors.New("unable to type assert gctscript.VM")
|
||||
return nil, common.GetTypeAssertError("*gctscript.VM", v)
|
||||
}
|
||||
err = vm.Shutdown()
|
||||
status := " terminated"
|
||||
|
||||
@@ -1885,8 +1885,8 @@ func TestGetDataHistoryJobSummary(t *testing.T) {
|
||||
if resp == nil { //nolint:staticcheck,nolintlint // SA5011 Ignore the nil warnings
|
||||
t.Fatal("expected job")
|
||||
}
|
||||
if !strings.EqualFold(resp.Nickname, "TestGetDataHistoryJobSummary") { //nolint:staticcheck,nolintlint // SA5011 Ignore the nil warnings
|
||||
t.Fatalf("received %v, expected %v", "TestGetDataHistoryJobSummary", resp.Nickname)
|
||||
if resp.Nickname == "" {
|
||||
t.Fatalf("received %v, expected %v", "", dhj.Nickname)
|
||||
}
|
||||
if resp.ResultSummaries == nil { //nolint:staticcheck,nolintlint // SA5011 Ignore the nil warnings
|
||||
t.Fatalf("received %v, expected %v", nil, "result summaries slice")
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/thrasher-corp/gocryptotrader/common"
|
||||
dbwithdraw "github.com/thrasher-corp/gocryptotrader/database/repository/withdraw"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/currencystate"
|
||||
@@ -101,7 +102,7 @@ func (m *WithdrawManager) WithdrawalEventByID(id string) (*withdraw.Response, er
|
||||
if v := withdraw.Cache.Get(id); v != nil {
|
||||
wdResp, ok := v.(*withdraw.Response)
|
||||
if !ok {
|
||||
return nil, errors.New("unable to type assert withdraw.Response")
|
||||
return nil, common.GetTypeAssertError("*withdraw.Response", v)
|
||||
}
|
||||
return wdResp, nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user