mirror of
https://github.com/d0zingcat/gocryptotrader.git
synced 2026-06-01 07:26:48 +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:
@@ -566,7 +566,7 @@ func fillData(exchName, checkType string, data interface{}) (ExchangeInfo, error
|
||||
case github:
|
||||
tempData, ok := data.(GithubData)
|
||||
if !ok {
|
||||
return ExchangeInfo{}, errors.New("unable to type assert GithubData")
|
||||
return ExchangeInfo{}, common.GetTypeAssertError("GithubData", data)
|
||||
}
|
||||
tempSha, err := getSha(path)
|
||||
if err != nil {
|
||||
@@ -583,7 +583,7 @@ func fillData(exchName, checkType string, data interface{}) (ExchangeInfo, error
|
||||
case htmlScrape:
|
||||
tempData, ok := data.(HTMLScrapingData)
|
||||
if !ok {
|
||||
return ExchangeInfo{}, errors.New("unable to type assert HTMLScrapingData")
|
||||
return ExchangeInfo{}, common.GetTypeAssertError("HTMLScrapingData", data)
|
||||
}
|
||||
checkStr, err := checkChangeLog(&tempData)
|
||||
if err != nil {
|
||||
@@ -1280,7 +1280,7 @@ func sendGetReq(path string, result interface{}) error {
|
||||
Verbose: verbose}
|
||||
return requester.SendPayload(context.Background(), request.Unset, func() (*request.Item, error) {
|
||||
return item, nil
|
||||
})
|
||||
}, request.UnauthenticatedRequest)
|
||||
}
|
||||
|
||||
// sendAuthReq sends auth req
|
||||
@@ -1298,7 +1298,7 @@ func sendAuthReq(method, path string, result interface{}) error {
|
||||
Verbose: verbose}
|
||||
return requester.SendPayload(context.Background(), request.Unset, func() (*request.Item, error) {
|
||||
return item, nil
|
||||
})
|
||||
}, request.AuthenticatedRequest)
|
||||
}
|
||||
|
||||
// trelloGetBoardID gets all board ids on trello for a given user
|
||||
|
||||
@@ -95,7 +95,7 @@ func canTestTrello() bool {
|
||||
|
||||
func TestCheckUpdates(t *testing.T) {
|
||||
if !canUpdateTrello() || !canTestTrello() {
|
||||
t.Skip()
|
||||
t.Skip("cannot update or test trello, skipping")
|
||||
}
|
||||
err := checkUpdates(testJSONFile)
|
||||
if err != nil {
|
||||
@@ -502,7 +502,7 @@ func TestGetSha(t *testing.T) {
|
||||
|
||||
func TestCheckBoardID(t *testing.T) {
|
||||
if !areTestAPIKeysSet() {
|
||||
t.Skip()
|
||||
t.Skip("API Keys unset, skipping")
|
||||
}
|
||||
a, err := trelloCheckBoardID()
|
||||
if err != nil {
|
||||
@@ -515,7 +515,7 @@ func TestCheckBoardID(t *testing.T) {
|
||||
|
||||
func TestTrelloGetLists(t *testing.T) {
|
||||
if !areTestAPIKeysSet() {
|
||||
t.Skip()
|
||||
t.Skip("API Keys unset, skipping")
|
||||
}
|
||||
if _, err := trelloGetLists(); err != nil {
|
||||
t.Error(err)
|
||||
@@ -524,7 +524,7 @@ func TestTrelloGetLists(t *testing.T) {
|
||||
|
||||
func TestGetAllCards(t *testing.T) {
|
||||
if !areTestAPIKeysSet() {
|
||||
t.Skip()
|
||||
t.Skip("API Keys unset, skipping")
|
||||
}
|
||||
if _, err := trelloGetAllCards(); err != nil {
|
||||
t.Error(err)
|
||||
@@ -533,7 +533,7 @@ func TestGetAllCards(t *testing.T) {
|
||||
|
||||
func TestGetAllChecklists(t *testing.T) {
|
||||
if !areTestAPIKeysSet() {
|
||||
t.Skip()
|
||||
t.Skip("API Keys unset, skipping")
|
||||
}
|
||||
if _, err := trelloGetAllChecklists(); err != nil {
|
||||
t.Error(err)
|
||||
@@ -542,10 +542,10 @@ func TestGetAllChecklists(t *testing.T) {
|
||||
|
||||
func TestTrelloGetAllBoards(t *testing.T) {
|
||||
if !areTestAPIKeysSet() {
|
||||
t.Skip()
|
||||
t.Skip("API Keys unset, skipping")
|
||||
}
|
||||
if trelloBoardID != "" || testBoardName != "" {
|
||||
t.Skip()
|
||||
t.Skip("trello details empty, skipping")
|
||||
}
|
||||
if _, err := trelloGetBoardID(); err != nil {
|
||||
t.Error(err)
|
||||
@@ -554,7 +554,7 @@ func TestTrelloGetAllBoards(t *testing.T) {
|
||||
|
||||
func TestCreateNewList(t *testing.T) {
|
||||
if !areTestAPIKeysSet() {
|
||||
t.Skip()
|
||||
t.Skip("API Keys unset, skipping")
|
||||
}
|
||||
if err := trelloCreateNewList(); err != nil {
|
||||
t.Error(err)
|
||||
@@ -563,7 +563,7 @@ func TestCreateNewList(t *testing.T) {
|
||||
|
||||
func TestTrelloCreateNewCard(t *testing.T) {
|
||||
if !areTestAPIKeysSet() {
|
||||
t.Skip()
|
||||
t.Skip("API Keys unset, skipping")
|
||||
}
|
||||
if err := trelloCreateNewCard(); err != nil {
|
||||
t.Error(err)
|
||||
@@ -572,7 +572,7 @@ func TestTrelloCreateNewCard(t *testing.T) {
|
||||
|
||||
func TestCreateNewChecklist(t *testing.T) {
|
||||
if !areTestAPIKeysSet() {
|
||||
t.Skip()
|
||||
t.Skip("API Keys unset, skipping")
|
||||
}
|
||||
if err := trelloCreateNewChecklist(); err != nil {
|
||||
t.Error(err)
|
||||
@@ -590,7 +590,7 @@ func TestWriteAuthVars(t *testing.T) {
|
||||
|
||||
func TestCreateNewCheck(t *testing.T) {
|
||||
if !canTestTrello() {
|
||||
t.Skip()
|
||||
t.Skip("cannot test trello, skipping")
|
||||
}
|
||||
err := trelloCreateNewCheck("Gemini")
|
||||
if err != nil {
|
||||
@@ -600,7 +600,7 @@ func TestCreateNewCheck(t *testing.T) {
|
||||
|
||||
func TestUpdateCheckItem(t *testing.T) {
|
||||
if !canTestTrello() {
|
||||
t.Skip()
|
||||
t.Skip("cannot test trello, skipping")
|
||||
}
|
||||
a, err := trelloGetChecklistItems()
|
||||
if err != nil {
|
||||
@@ -620,7 +620,7 @@ func TestUpdateCheckItem(t *testing.T) {
|
||||
|
||||
func TestGetChecklistItems(t *testing.T) {
|
||||
if !canTestTrello() {
|
||||
t.Skip()
|
||||
t.Skip("cannot test trello, skipping")
|
||||
}
|
||||
_, err := trelloGetChecklistItems()
|
||||
if err != nil {
|
||||
@@ -642,7 +642,7 @@ func TestSetAuthVars(t *testing.T) {
|
||||
|
||||
func TestTrelloDeleteCheckItems(t *testing.T) {
|
||||
if !areTestAPIKeysSet() {
|
||||
t.Skip()
|
||||
t.Skip("API Keys unset, skipping")
|
||||
}
|
||||
err := trelloDeleteCheckItem("")
|
||||
if err != nil {
|
||||
|
||||
@@ -17,6 +17,8 @@ import (
|
||||
"github.com/thrasher-corp/gocryptotrader/common"
|
||||
"github.com/thrasher-corp/gocryptotrader/common/file"
|
||||
"github.com/thrasher-corp/gocryptotrader/core"
|
||||
"golang.org/x/text/cases"
|
||||
"golang.org/x/text/language"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -492,7 +494,7 @@ func GetPackageName(name string, capital bool) string {
|
||||
i = len(newStrings) - 1
|
||||
}
|
||||
if capital {
|
||||
return strings.Replace(strings.Title(newStrings[i]), "_", " ", -1)
|
||||
return strings.Replace(cases.Title(language.English).String(newStrings[i]), "_", " ", -1)
|
||||
}
|
||||
return newStrings[i]
|
||||
}
|
||||
|
||||
@@ -16,6 +16,8 @@ import (
|
||||
"github.com/thrasher-corp/gocryptotrader/config"
|
||||
"github.com/thrasher-corp/gocryptotrader/core"
|
||||
"github.com/thrasher-corp/gocryptotrader/currency"
|
||||
"golang.org/x/text/cases"
|
||||
"golang.org/x/text/language"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -146,7 +148,7 @@ func makeExchange(exchangeDirectory string, configTestFile *config.Config, exch
|
||||
|
||||
fmt.Printf("Output directory: %s\n", exchangeDirectory)
|
||||
|
||||
exch.CapitalName = strings.Title(exch.Name)
|
||||
exch.CapitalName = cases.Title(language.English).String(exch.Name)
|
||||
exch.Variable = exch.Name[0:2]
|
||||
newExchConfig := &config.Exchange{}
|
||||
newExchConfig.Name = exch.CapitalName
|
||||
|
||||
@@ -385,12 +385,12 @@ func ({{.Variable}} *{{.CapitalName}}) FetchAccountInfo(ctx context.Context, ass
|
||||
|
||||
// GetFundingHistory returns funding history, deposits and
|
||||
// withdrawals
|
||||
func ({{.Variable}} *{{.CapitalName}}) GetFundingHistory(ctx context.Context) ([]exchange.FundHistory, error) {
|
||||
func ({{.Variable}} *{{.CapitalName}}) GetAccountFundingHistory(ctx context.Context) ([]exchange.FundingHistory, error) {
|
||||
return nil, common.ErrNotYetImplemented
|
||||
}
|
||||
|
||||
// GetWithdrawalsHistory returns previous withdrawals data
|
||||
func ({{.Variable}} *{{.CapitalName}}) GetWithdrawalsHistory(ctx context.Context, c currency.Code, a asset.Item) (resp []exchange.WithdrawalHistory, err error) {
|
||||
func ({{.Variable}} *{{.CapitalName}}) GetWithdrawalsHistory(ctx context.Context, c currency.Code, a asset.Item) ([]exchange.WithdrawalHistory, error) {
|
||||
return nil, common.ErrNotYetImplemented
|
||||
}
|
||||
|
||||
@@ -400,10 +400,17 @@ func ({{.Variable}} *{{.CapitalName}}) GetRecentTrades(ctx context.Context, p cu
|
||||
}
|
||||
|
||||
// GetHistoricTrades returns historic trade data within the timeframe provided
|
||||
func ({{.Variable}} *{{.CapitalName}}) GetHistoricTrades (ctx context.Context, p currency.Pair, assetType asset.Item, timestampStart, timestampEnd time.Time) ([]trade.Data, error) {
|
||||
func ({{.Variable}} *{{.CapitalName}}) GetHistoricTrades(ctx context.Context, p currency.Pair, assetType asset.Item, timestampStart, timestampEnd time.Time) ([]trade.Data, error) {
|
||||
return nil, common.ErrNotYetImplemented
|
||||
}
|
||||
|
||||
|
||||
// GetServerTime returns the current exchange server time.
|
||||
func ({{.Variable}} *{{.CapitalName}}) GetServerTime(ctx context.Context, a asset.Item) (time.Time, error) {
|
||||
return time.Time{}, common.ErrNotYetImplemented
|
||||
}
|
||||
|
||||
|
||||
// SubmitOrder submits a new order
|
||||
func ({{.Variable}} *{{.CapitalName}}) SubmitOrder(ctx context.Context, s *order.Submit) (*order.SubmitResponse, error) {
|
||||
if err := s.Validate(); err != nil {
|
||||
@@ -447,8 +454,8 @@ func ({{.Variable}} *{{.CapitalName}}) CancelOrder(ctx context.Context, ord *ord
|
||||
}
|
||||
|
||||
// CancelBatchOrders cancels orders by their corresponding ID numbers
|
||||
func ({{.Variable}} *{{.CapitalName}}) CancelBatchOrders(ctx context.Context, orders []order.Cancel) (order.CancelBatchResponse, error) {
|
||||
return order.CancelBatchResponse{}, common.ErrNotYetImplemented
|
||||
func ({{.Variable}} *{{.CapitalName}}) CancelBatchOrders(ctx context.Context, orders []order.Cancel) (*order.CancelBatchResponse, error) {
|
||||
return nil, common.ErrNotYetImplemented
|
||||
}
|
||||
|
||||
// CancelAllOrders cancels all orders associated with a currency pair
|
||||
@@ -460,8 +467,8 @@ func ({{.Variable}} *{{.CapitalName}}) CancelAllOrders(ctx context.Context, orde
|
||||
}
|
||||
|
||||
// GetOrderInfo returns order information based on order ID
|
||||
func ({{.Variable}} *{{.CapitalName}}) GetOrderInfo(ctx context.Context, orderID string, pair currency.Pair, assetType asset.Item) (order.Detail, error) {
|
||||
return order.Detail{}, common.ErrNotYetImplemented
|
||||
func ({{.Variable}} *{{.CapitalName}}) GetOrderInfo(ctx context.Context, orderID string, pair currency.Pair, assetType asset.Item) (*order.Detail, error) {
|
||||
return nil, common.ErrNotYetImplemented
|
||||
}
|
||||
|
||||
// GetDepositAddress returns a deposit address for a specified currency
|
||||
@@ -497,7 +504,7 @@ func ({{.Variable}} *{{.CapitalName}}) WithdrawFiatFundsToInternationalBank(ctx
|
||||
}
|
||||
|
||||
// GetActiveOrders retrieves any orders that are active/open
|
||||
func ({{.Variable}} *{{.CapitalName}}) GetActiveOrders(ctx context.Context, getOrdersRequest *order.GetOrdersRequest) (order.FilteredOrders, error) {
|
||||
func ({{.Variable}} *{{.CapitalName}}) GetActiveOrders(ctx context.Context, getOrdersRequest *order.MultiOrderRequest) (order.FilteredOrders, error) {
|
||||
// if err := getOrdersRequest.Validate(); err != nil {
|
||||
// return nil, err
|
||||
// }
|
||||
@@ -506,7 +513,7 @@ func ({{.Variable}} *{{.CapitalName}}) GetActiveOrders(ctx context.Context, getO
|
||||
|
||||
// GetOrderHistory retrieves account order information
|
||||
// Can Limit response to specific order status
|
||||
func ({{.Variable}} *{{.CapitalName}}) GetOrderHistory(ctx context.Context, getOrdersRequest *order.GetOrdersRequest) (order.FilteredOrders, error) {
|
||||
func ({{.Variable}} *{{.CapitalName}}) GetOrderHistory(ctx context.Context, getOrdersRequest *order.MultiOrderRequest) (order.FilteredOrders, error) {
|
||||
// if err := getOrdersRequest.Validate(); err != nil {
|
||||
// return nil, err
|
||||
// }
|
||||
|
||||
@@ -130,17 +130,18 @@ func testWrappers(e exchange.IBotExchange) ([]string, error) {
|
||||
|
||||
for y := range outputs {
|
||||
incoming := outputs[y].Interface()
|
||||
if reflect.TypeOf(incoming) == errType {
|
||||
err, ok := incoming.(error)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("%s type assertion failure for %v", name, incoming)
|
||||
}
|
||||
if errors.Is(err, common.ErrNotYetImplemented) {
|
||||
funcs = append(funcs, name)
|
||||
}
|
||||
// found error; there should not be another error in this slice.
|
||||
break
|
||||
if reflect.TypeOf(incoming) != errType {
|
||||
continue
|
||||
}
|
||||
err, ok := incoming.(error)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("%s type assertion failure for %v", name, incoming)
|
||||
}
|
||||
if errors.Is(err, common.ErrNotYetImplemented) {
|
||||
funcs = append(funcs, name)
|
||||
}
|
||||
// found error; there should not be another error in this slice.
|
||||
break
|
||||
}
|
||||
}
|
||||
return funcs, nil
|
||||
|
||||
@@ -561,15 +561,15 @@ func testWrappers(e exchange.IBotExchange, base *exchange.Base, config *Config)
|
||||
Response: jsonifyInterface([]interface{}{fetchAccountInfoResponse}),
|
||||
})
|
||||
|
||||
var getFundingHistoryResponse []exchange.FundHistory
|
||||
getFundingHistoryResponse, err = e.GetFundingHistory(context.TODO())
|
||||
var getFundingHistoryResponse []exchange.FundingHistory
|
||||
getFundingHistoryResponse, err = e.GetAccountFundingHistory(context.TODO())
|
||||
msg = ""
|
||||
if err != nil {
|
||||
msg = err.Error()
|
||||
responseContainer.ErrorCount++
|
||||
}
|
||||
responseContainer.EndpointResponses = append(responseContainer.EndpointResponses, EndpointResponse{
|
||||
Function: "GetFundingHistory",
|
||||
Function: "GetAccountFundingHistory",
|
||||
Error: msg,
|
||||
Response: jsonifyInterface([]interface{}{getFundingHistoryResponse}),
|
||||
})
|
||||
@@ -667,7 +667,7 @@ func testWrappers(e exchange.IBotExchange, base *exchange.Base, config *Config)
|
||||
AssetType: assetTypes[i],
|
||||
})
|
||||
|
||||
var CancelBatchOrdersResponse order.CancelBatchResponse
|
||||
var CancelBatchOrdersResponse *order.CancelBatchResponse
|
||||
CancelBatchOrdersResponse, err = e.CancelBatchOrders(context.TODO(), request)
|
||||
msg = ""
|
||||
if err != nil {
|
||||
@@ -695,7 +695,7 @@ func testWrappers(e exchange.IBotExchange, base *exchange.Base, config *Config)
|
||||
Response: jsonifyInterface([]interface{}{cancellAllOrdersResponse}),
|
||||
})
|
||||
|
||||
var r15 order.Detail
|
||||
var r15 *order.Detail
|
||||
r15, err = e.GetOrderInfo(context.TODO(), config.OrderSubmission.OrderID, p, assetTypes[i])
|
||||
msg = ""
|
||||
if err != nil {
|
||||
@@ -709,7 +709,7 @@ func testWrappers(e exchange.IBotExchange, base *exchange.Base, config *Config)
|
||||
Response: jsonifyInterface([]interface{}{r15}),
|
||||
})
|
||||
|
||||
historyRequest := order.GetOrdersRequest{
|
||||
historyRequest := order.MultiOrderRequest{
|
||||
Type: testOrderType,
|
||||
Side: testOrderSide,
|
||||
Pairs: []currency.Pair{p},
|
||||
@@ -731,7 +731,7 @@ func testWrappers(e exchange.IBotExchange, base *exchange.Base, config *Config)
|
||||
Response: jsonifyInterface([]interface{}{getOrderHistoryResponse}),
|
||||
})
|
||||
|
||||
orderRequest := order.GetOrdersRequest{
|
||||
orderRequest := order.MultiOrderRequest{
|
||||
Type: testOrderType,
|
||||
Side: testOrderSide,
|
||||
Pairs: []currency.Pair{p},
|
||||
|
||||
@@ -0,0 +1,669 @@
|
||||
package exchangewrapperstandards
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"os"
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/thrasher-corp/gocryptotrader/common"
|
||||
"github.com/thrasher-corp/gocryptotrader/config"
|
||||
"github.com/thrasher-corp/gocryptotrader/currency"
|
||||
"github.com/thrasher-corp/gocryptotrader/engine"
|
||||
exchange "github.com/thrasher-corp/gocryptotrader/exchanges"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/account"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/deposit"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/kline"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/order"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/request"
|
||||
"github.com/thrasher-corp/gocryptotrader/portfolio/banking"
|
||||
"github.com/thrasher-corp/gocryptotrader/portfolio/withdraw"
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
// only run testing suite for one CI/CD environment
|
||||
if isAppVeyor() || is32BitJob() {
|
||||
return
|
||||
}
|
||||
request.MaxRequestJobs = 200
|
||||
os.Exit(m.Run())
|
||||
}
|
||||
|
||||
func TestAllExchangeWrappers(t *testing.T) {
|
||||
t.Parallel()
|
||||
cfg := config.GetConfig()
|
||||
err := cfg.LoadConfig("../../testdata/configtest.json", true)
|
||||
if err != nil {
|
||||
t.Fatal("load config error", err)
|
||||
}
|
||||
for i := range cfg.Exchanges {
|
||||
name := strings.ToLower(cfg.Exchanges[i].Name)
|
||||
t.Run(name+" wrapper tests", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
if common.StringDataContains(unsupportedExchangeNames, name) {
|
||||
t.Skipf("skipping unsupported exchange %v", name)
|
||||
}
|
||||
ctx := context.Background()
|
||||
if isCITest() && common.StringDataContains(blockedCIExchanges, name) {
|
||||
// rather than skipping tests where execution is blocked, provide an expired
|
||||
// context, so no executions can take place
|
||||
var cancelFn context.CancelFunc
|
||||
ctx, cancelFn = context.WithTimeout(context.Background(), 0)
|
||||
cancelFn()
|
||||
}
|
||||
exch, assetPairs := setupExchange(ctx, t, name, cfg)
|
||||
executeExchangeWrapperTests(ctx, t, exch, assetPairs)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func setupExchange(ctx context.Context, t *testing.T, name string, cfg *config.Config) (exchange.IBotExchange, []assetPair) {
|
||||
t.Helper()
|
||||
em := engine.NewExchangeManager()
|
||||
exch, err := em.NewExchangeByName(name)
|
||||
if err != nil {
|
||||
t.Fatalf("Cannot setup %v NewExchangeByName %v", name, err)
|
||||
}
|
||||
var exchCfg *config.Exchange
|
||||
exchCfg, err = cfg.GetExchangeConfig(name)
|
||||
if err != nil {
|
||||
t.Fatalf("Cannot setup %v GetExchangeConfig %v", name, err)
|
||||
}
|
||||
exch.SetDefaults()
|
||||
exchCfg.API.AuthenticatedSupport = true
|
||||
exchCfg.API.Credentials = getExchangeCredentials(name)
|
||||
|
||||
err = exch.Setup(exchCfg)
|
||||
if err != nil {
|
||||
t.Fatalf("Cannot setup %v exchange Setup %v", name, err)
|
||||
}
|
||||
|
||||
err = exch.UpdateTradablePairs(ctx, true)
|
||||
if err != nil && !errors.Is(err, context.DeadlineExceeded) {
|
||||
t.Fatalf("Cannot setup %v UpdateTradablePairs %v", name, err)
|
||||
}
|
||||
b := exch.GetBase()
|
||||
|
||||
assets := b.CurrencyPairs.GetAssetTypes(false)
|
||||
if len(assets) == 0 {
|
||||
t.Fatalf("Cannot setup %v, exchange has no assets", name)
|
||||
}
|
||||
for j := range assets {
|
||||
err = b.CurrencyPairs.SetAssetEnabled(assets[j], true)
|
||||
if err != nil && !errors.Is(err, currency.ErrAssetAlreadyEnabled) {
|
||||
t.Fatalf("Cannot setup %v SetAssetEnabled %v", name, err)
|
||||
}
|
||||
}
|
||||
|
||||
// Add +1 to len to verify that exchanges can handle requests with unset pairs and assets
|
||||
assetPairs := make([]assetPair, 0, len(assets)+1)
|
||||
for j := range assets {
|
||||
var pairs currency.Pairs
|
||||
pairs, err = b.CurrencyPairs.GetPairs(assets[j], false)
|
||||
if err != nil {
|
||||
t.Fatalf("Cannot setup %v asset %v GetPairs %v", name, assets[j], err)
|
||||
}
|
||||
var p currency.Pair
|
||||
p, err = getPairFromPairs(t, pairs)
|
||||
if err != nil {
|
||||
if errors.Is(err, currency.ErrCurrencyPairsEmpty) {
|
||||
continue
|
||||
}
|
||||
t.Fatalf("Cannot setup %v asset %v getPairFromPairs %v", name, assets[j], err)
|
||||
}
|
||||
err = b.CurrencyPairs.EnablePair(assets[j], p)
|
||||
if err != nil && !errors.Is(err, currency.ErrPairAlreadyEnabled) {
|
||||
t.Fatalf("Cannot setup %v asset %v EnablePair %v", name, assets[j], err)
|
||||
}
|
||||
p, err = b.FormatExchangeCurrency(p, assets[j])
|
||||
if err != nil {
|
||||
t.Fatalf("Cannot setup %v asset %v FormatExchangeCurrency %v", name, assets[j], err)
|
||||
}
|
||||
p, err = disruptFormatting(t, p)
|
||||
if err != nil {
|
||||
t.Fatalf("Cannot setup %v asset %v disruptFormatting %v", name, assets[j], err)
|
||||
}
|
||||
assetPairs = append(assetPairs, assetPair{
|
||||
Pair: p,
|
||||
Asset: assets[j],
|
||||
})
|
||||
}
|
||||
assetPairs = append(assetPairs, assetPair{})
|
||||
|
||||
return exch, assetPairs
|
||||
}
|
||||
|
||||
// isUnacceptableError sentences errs to 10 years dungeon if unacceptable
|
||||
func isUnacceptableError(t *testing.T, err error) error {
|
||||
t.Helper()
|
||||
for i := range acceptableErrors {
|
||||
if errors.Is(err, acceptableErrors[i]) {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
for i := range warningErrors {
|
||||
if errors.Is(err, warningErrors[i]) {
|
||||
t.Log(err)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func executeExchangeWrapperTests(ctx context.Context, t *testing.T, exch exchange.IBotExchange, assetParams []assetPair) {
|
||||
t.Helper()
|
||||
iExchange := reflect.TypeOf(&exch).Elem()
|
||||
actualExchange := reflect.ValueOf(exch)
|
||||
|
||||
for x := 0; x < iExchange.NumMethod(); x++ {
|
||||
methodName := iExchange.Method(x).Name
|
||||
if _, ok := excludedMethodNames[methodName]; ok {
|
||||
continue
|
||||
}
|
||||
method := actualExchange.MethodByName(methodName)
|
||||
|
||||
var assetLen int
|
||||
for y := 0; y < method.Type().NumIn(); y++ {
|
||||
input := method.Type().In(y)
|
||||
if input.AssignableTo(assetParam) ||
|
||||
input.AssignableTo(orderSubmitParam) ||
|
||||
input.AssignableTo(orderModifyParam) ||
|
||||
input.AssignableTo(orderCancelParam) ||
|
||||
input.AssignableTo(orderCancelsParam) ||
|
||||
input.AssignableTo(getOrdersRequestParam) {
|
||||
// this allows wrapper functions that support assets types
|
||||
// to be tested with all supported assets
|
||||
assetLen = len(assetParams) - 1
|
||||
}
|
||||
}
|
||||
tt := time.Now()
|
||||
e := time.Date(tt.Year(), tt.Month(), tt.Day()-1, 0, 0, 0, 0, time.UTC)
|
||||
s := e.Add(-time.Hour * 24 * 2)
|
||||
if methodName == "GetHistoricTrades" {
|
||||
// limit trade history
|
||||
e = time.Now()
|
||||
s = e.Add(-time.Minute * 3)
|
||||
}
|
||||
for y := 0; y <= assetLen; y++ {
|
||||
inputs := make([]reflect.Value, method.Type().NumIn())
|
||||
argGenerator := &MethodArgumentGenerator{
|
||||
Exchange: exch,
|
||||
AssetParams: assetParams[y],
|
||||
MethodName: methodName,
|
||||
Start: s,
|
||||
End: e,
|
||||
}
|
||||
for z := 0; z < method.Type().NumIn(); z++ {
|
||||
argGenerator.MethodInputType = method.Type().In(z)
|
||||
generatedArg := generateMethodArg(ctx, t, argGenerator)
|
||||
inputs[z] = *generatedArg
|
||||
}
|
||||
assetY := assetParams[y].Asset.String()
|
||||
pairY := assetParams[y].Pair.String()
|
||||
t.Run(methodName+"-"+assetY+"-"+pairY, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
CallExchangeMethod(t, method, inputs, methodName, exch)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// CallExchangeMethod will call an exchange's method using generated arguments
|
||||
// and determine if the error is friendly
|
||||
func CallExchangeMethod(t *testing.T, methodToCall reflect.Value, methodValues []reflect.Value, methodName string, exch exchange.IBotExchange) {
|
||||
t.Helper()
|
||||
outputs := methodToCall.Call(methodValues)
|
||||
for i := range outputs {
|
||||
outputInterface := outputs[i].Interface()
|
||||
err, ok := outputInterface.(error)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
if isUnacceptableError(t, err) != nil {
|
||||
literalInputs := make([]interface{}, len(methodValues))
|
||||
for j := range methodValues {
|
||||
literalInputs[j] = methodValues[j].Interface()
|
||||
}
|
||||
t.Errorf("%v Func '%v' Error: '%v'. Inputs: %v.", exch.GetName(), methodName, err, literalInputs)
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// MethodArgumentGenerator is used to create arguments for
|
||||
// an IBotExchange method
|
||||
type MethodArgumentGenerator struct {
|
||||
Exchange exchange.IBotExchange
|
||||
AssetParams assetPair
|
||||
MethodInputType reflect.Type
|
||||
MethodName string
|
||||
Start time.Time
|
||||
End time.Time
|
||||
StartTimeSet bool
|
||||
argNum int64
|
||||
}
|
||||
|
||||
var (
|
||||
currencyPairParam = reflect.TypeOf((*currency.Pair)(nil)).Elem()
|
||||
klineParam = reflect.TypeOf((*kline.Interval)(nil)).Elem()
|
||||
contextParam = reflect.TypeOf((*context.Context)(nil)).Elem()
|
||||
timeParam = reflect.TypeOf((*time.Time)(nil)).Elem()
|
||||
codeParam = reflect.TypeOf((*currency.Code)(nil)).Elem()
|
||||
currencyPairsParam = reflect.TypeOf((*currency.Pairs)(nil)).Elem()
|
||||
withdrawRequestParam = reflect.TypeOf((**withdraw.Request)(nil)).Elem()
|
||||
stringParam = reflect.TypeOf((*string)(nil)).Elem()
|
||||
feeBuilderParam = reflect.TypeOf((**exchange.FeeBuilder)(nil)).Elem()
|
||||
credentialsParam = reflect.TypeOf((**account.Credentials)(nil)).Elem()
|
||||
// types with asset in params
|
||||
assetParam = reflect.TypeOf((*asset.Item)(nil)).Elem()
|
||||
orderSubmitParam = reflect.TypeOf((**order.Submit)(nil)).Elem()
|
||||
orderModifyParam = reflect.TypeOf((**order.Modify)(nil)).Elem()
|
||||
orderCancelParam = reflect.TypeOf((**order.Cancel)(nil)).Elem()
|
||||
orderCancelsParam = reflect.TypeOf((*[]order.Cancel)(nil)).Elem()
|
||||
getOrdersRequestParam = reflect.TypeOf((**order.MultiOrderRequest)(nil)).Elem()
|
||||
)
|
||||
|
||||
// generateMethodArg determines the argument type and returns a pre-made
|
||||
// response, else an empty version of the type
|
||||
func generateMethodArg(ctx context.Context, t *testing.T, argGenerator *MethodArgumentGenerator) *reflect.Value {
|
||||
t.Helper()
|
||||
exchName := strings.ToLower(argGenerator.Exchange.GetName())
|
||||
var input reflect.Value
|
||||
switch {
|
||||
case argGenerator.MethodInputType.AssignableTo(stringParam):
|
||||
switch argGenerator.MethodName {
|
||||
case "GetDepositAddress":
|
||||
if argGenerator.argNum == 2 {
|
||||
// account type
|
||||
input = reflect.ValueOf("trading")
|
||||
} else {
|
||||
// Crypto Chain
|
||||
input = reflect.ValueOf(cryptoChainPerExchange[exchName])
|
||||
}
|
||||
default:
|
||||
// OrderID
|
||||
input = reflect.ValueOf("1337")
|
||||
}
|
||||
case argGenerator.MethodInputType.AssignableTo(credentialsParam):
|
||||
input = reflect.ValueOf(&account.Credentials{
|
||||
Key: "test",
|
||||
Secret: "test",
|
||||
ClientID: "test",
|
||||
PEMKey: "test",
|
||||
SubAccount: "test",
|
||||
OneTimePassword: "test",
|
||||
})
|
||||
case argGenerator.MethodInputType.Implements(contextParam):
|
||||
// Need to deploy a context.Context value as nil value is not
|
||||
// checked throughout codebase.
|
||||
input = reflect.ValueOf(ctx)
|
||||
case argGenerator.MethodInputType.AssignableTo(feeBuilderParam):
|
||||
input = reflect.ValueOf(&exchange.FeeBuilder{
|
||||
FeeType: exchange.OfflineTradeFee,
|
||||
Amount: 1337,
|
||||
PurchasePrice: 1337,
|
||||
Pair: argGenerator.AssetParams.Pair,
|
||||
})
|
||||
case argGenerator.MethodInputType.AssignableTo(currencyPairParam):
|
||||
input = reflect.ValueOf(argGenerator.AssetParams.Pair)
|
||||
case argGenerator.MethodInputType.AssignableTo(assetParam):
|
||||
input = reflect.ValueOf(argGenerator.AssetParams.Asset)
|
||||
case argGenerator.MethodInputType.AssignableTo(klineParam):
|
||||
input = reflect.ValueOf(kline.OneDay)
|
||||
case argGenerator.MethodInputType.AssignableTo(codeParam):
|
||||
if argGenerator.MethodName == "GetAvailableTransferChains" {
|
||||
input = reflect.ValueOf(currency.ETH)
|
||||
} else {
|
||||
input = reflect.ValueOf(argGenerator.AssetParams.Pair.Base)
|
||||
}
|
||||
case argGenerator.MethodInputType.AssignableTo(timeParam):
|
||||
if !argGenerator.StartTimeSet {
|
||||
input = reflect.ValueOf(argGenerator.Start)
|
||||
argGenerator.StartTimeSet = true
|
||||
} else {
|
||||
input = reflect.ValueOf(argGenerator.End)
|
||||
}
|
||||
case argGenerator.MethodInputType.AssignableTo(currencyPairsParam):
|
||||
b := argGenerator.Exchange.GetBase()
|
||||
if argGenerator.AssetParams.Asset != asset.Empty {
|
||||
input = reflect.ValueOf(b.CurrencyPairs.Pairs[argGenerator.AssetParams.Asset].Available)
|
||||
} else {
|
||||
input = reflect.ValueOf(currency.Pairs{
|
||||
argGenerator.AssetParams.Pair,
|
||||
})
|
||||
}
|
||||
case argGenerator.MethodInputType.AssignableTo(withdrawRequestParam):
|
||||
req := &withdraw.Request{
|
||||
Exchange: exchName,
|
||||
Description: "1337",
|
||||
Amount: 1,
|
||||
ClientOrderID: "1337",
|
||||
}
|
||||
if argGenerator.MethodName == "WithdrawCryptocurrencyFunds" {
|
||||
req.Type = withdraw.Crypto
|
||||
switch {
|
||||
case !isFiat(t, argGenerator.AssetParams.Pair.Base.Item.Lower):
|
||||
req.Currency = argGenerator.AssetParams.Pair.Base
|
||||
case !isFiat(t, argGenerator.AssetParams.Pair.Quote.Item.Lower):
|
||||
req.Currency = argGenerator.AssetParams.Pair.Quote
|
||||
default:
|
||||
req.Currency = currency.ETH
|
||||
}
|
||||
|
||||
req.Crypto = withdraw.CryptoRequest{
|
||||
Address: "1337",
|
||||
AddressTag: "1337",
|
||||
Chain: cryptoChainPerExchange[exchName],
|
||||
}
|
||||
} else {
|
||||
req.Type = withdraw.Fiat
|
||||
b := argGenerator.Exchange.GetBase()
|
||||
if len(b.Config.BaseCurrencies) > 0 {
|
||||
req.Currency = b.Config.BaseCurrencies[0]
|
||||
} else {
|
||||
req.Currency = currency.USD
|
||||
}
|
||||
req.Fiat = withdraw.FiatRequest{
|
||||
Bank: banking.Account{
|
||||
Enabled: true,
|
||||
ID: "1337",
|
||||
BankName: "1337",
|
||||
BankAddress: "1337",
|
||||
BankPostalCode: "1337",
|
||||
BankPostalCity: "1337",
|
||||
BankCountry: "1337",
|
||||
AccountName: "1337",
|
||||
AccountNumber: "1337",
|
||||
SWIFTCode: "1337",
|
||||
IBAN: "1337",
|
||||
BSBNumber: "1337",
|
||||
BankCode: 1337,
|
||||
SupportedCurrencies: req.Currency.String(),
|
||||
SupportedExchanges: exchName,
|
||||
},
|
||||
IsExpressWire: false,
|
||||
RequiresIntermediaryBank: false,
|
||||
IntermediaryBankAccountNumber: 1338,
|
||||
IntermediaryBankName: "1338",
|
||||
IntermediaryBankAddress: "1338",
|
||||
IntermediaryBankCity: "1338",
|
||||
IntermediaryBankCountry: "1338",
|
||||
IntermediaryBankPostalCode: "1338",
|
||||
IntermediarySwiftCode: "1338",
|
||||
IntermediaryBankCode: 1338,
|
||||
IntermediaryIBAN: "1338",
|
||||
WireCurrency: "1338",
|
||||
}
|
||||
}
|
||||
input = reflect.ValueOf(req)
|
||||
case argGenerator.MethodInputType.AssignableTo(orderSubmitParam):
|
||||
input = reflect.ValueOf(&order.Submit{
|
||||
Exchange: exchName,
|
||||
Type: order.Limit,
|
||||
Side: order.Buy,
|
||||
Pair: argGenerator.AssetParams.Pair,
|
||||
AssetType: argGenerator.AssetParams.Asset,
|
||||
Price: 1337,
|
||||
Amount: 1,
|
||||
ClientID: "1337",
|
||||
ClientOrderID: "13371337",
|
||||
ImmediateOrCancel: true,
|
||||
})
|
||||
case argGenerator.MethodInputType.AssignableTo(orderModifyParam):
|
||||
input = reflect.ValueOf(&order.Modify{
|
||||
Exchange: exchName,
|
||||
Type: order.Limit,
|
||||
Side: order.Buy,
|
||||
Pair: argGenerator.AssetParams.Pair,
|
||||
AssetType: argGenerator.AssetParams.Asset,
|
||||
Price: 1337,
|
||||
Amount: 1,
|
||||
ClientOrderID: "13371337",
|
||||
OrderID: "1337",
|
||||
ImmediateOrCancel: true,
|
||||
})
|
||||
case argGenerator.MethodInputType.AssignableTo(orderCancelParam):
|
||||
input = reflect.ValueOf(&order.Cancel{
|
||||
Exchange: exchName,
|
||||
Type: order.Limit,
|
||||
Side: order.Buy,
|
||||
Pair: argGenerator.AssetParams.Pair,
|
||||
AssetType: argGenerator.AssetParams.Asset,
|
||||
OrderID: "1337",
|
||||
})
|
||||
case argGenerator.MethodInputType.AssignableTo(orderCancelsParam):
|
||||
input = reflect.ValueOf([]order.Cancel{
|
||||
{
|
||||
Exchange: exchName,
|
||||
Type: order.Market,
|
||||
Side: order.Buy,
|
||||
Pair: argGenerator.AssetParams.Pair,
|
||||
AssetType: argGenerator.AssetParams.Asset,
|
||||
OrderID: "1337",
|
||||
},
|
||||
})
|
||||
case argGenerator.MethodInputType.AssignableTo(getOrdersRequestParam):
|
||||
input = reflect.ValueOf(&order.MultiOrderRequest{
|
||||
Type: order.AnyType,
|
||||
Side: order.AnySide,
|
||||
FromOrderID: "1337",
|
||||
AssetType: argGenerator.AssetParams.Asset,
|
||||
Pairs: currency.Pairs{argGenerator.AssetParams.Pair},
|
||||
})
|
||||
default:
|
||||
input = reflect.Zero(argGenerator.MethodInputType)
|
||||
}
|
||||
argGenerator.argNum++
|
||||
|
||||
return &input
|
||||
}
|
||||
|
||||
// assetPair holds a currency pair associated with an asset
|
||||
type assetPair struct {
|
||||
Pair currency.Pair
|
||||
Asset asset.Item
|
||||
}
|
||||
|
||||
// excludedMethodNames represent the functions that are not
|
||||
// currently tested under this suite due to irrelevance
|
||||
// or not worth checking yet
|
||||
var excludedMethodNames = map[string]struct{}{
|
||||
"Setup": {}, // Is run via test setup
|
||||
"Start": {}, // Is run via test setup
|
||||
"SetDefaults": {}, // Is run via test setup
|
||||
"UpdateTradablePairs": {}, // Is run via test setup
|
||||
"GetDefaultConfig": {}, // Is run via test setup
|
||||
"FetchTradablePairs": {}, // Is run via test setup
|
||||
"GetCollateralCurrencyForContract": {}, // Not widely supported/implemented futures endpoint
|
||||
"GetCurrencyForRealisedPNL": {}, // Not widely supported/implemented futures endpoint
|
||||
"GetFuturesPositions": {}, // Not widely supported/implemented futures endpoint
|
||||
"GetFundingRates": {}, // Not widely supported/implemented futures endpoint
|
||||
"IsPerpetualFutureCurrency": {}, // Not widely supported/implemented futures endpoint
|
||||
"GetMarginRatesHistory": {}, // Not widely supported/implemented futures endpoint
|
||||
"CalculatePNL": {}, // Not widely supported/implemented futures endpoint
|
||||
"CalculateTotalCollateral": {}, // Not widely supported/implemented futures endpoint
|
||||
"ScaleCollateral": {}, // Not widely supported/implemented futures endpoint
|
||||
"GetPositionSummary": {}, // Not widely supported/implemented futures endpoint
|
||||
"AuthenticateWebsocket": {}, // Unnecessary websocket test
|
||||
"FlushWebsocketChannels": {}, // Unnecessary websocket test
|
||||
"UnsubscribeToWebsocketChannels": {}, // Unnecessary websocket test
|
||||
"SubscribeToWebsocketChannels": {}, // Unnecessary websocket test
|
||||
"GetOrderExecutionLimits": {}, // Not widely supported/implemented feature
|
||||
"UpdateCurrencyStates": {}, // Not widely supported/implemented feature
|
||||
"UpdateOrderExecutionLimits": {}, // Not widely supported/implemented feature
|
||||
"CanTradePair": {}, // Not widely supported/implemented feature
|
||||
"CanTrade": {}, // Not widely supported/implemented feature
|
||||
"CanWithdraw": {}, // Not widely supported/implemented feature
|
||||
"CanDeposit": {}, // Not widely supported/implemented feature
|
||||
"GetCurrencyStateSnapshot": {}, // Not widely supported/implemented feature
|
||||
"SetHTTPClientUserAgent": {}, // standard base implementation
|
||||
"SetClientProxyAddress": {}, // standard base implementation
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
var unsupportedExchangeNames = []string{
|
||||
"testexch",
|
||||
"alphapoint",
|
||||
"bitflyer", // Bitflyer has many "ErrNotYetImplemented, which is true, but not what we care to test for here
|
||||
"bittrex", // the api is about to expire in March, and we haven't updated it yet
|
||||
"itbit", // itbit has no way of retrieving pair data
|
||||
"okcoin international", // TODO add support for v5 and remove this entry
|
||||
}
|
||||
|
||||
// cryptoChainPerExchange holds the deposit address chain per exchange
|
||||
var cryptoChainPerExchange = map[string]string{
|
||||
"binanceus": "ERC20",
|
||||
"bybit": "ERC20",
|
||||
"gateio": "ERC20",
|
||||
}
|
||||
|
||||
// acceptable errors do not throw test errors, see below for why
|
||||
var acceptableErrors = []error{
|
||||
common.ErrFunctionNotSupported, // Shows API cannot perform function and developer has recognised this
|
||||
asset.ErrNotSupported, // Shows that valid and invalid asset types are handled
|
||||
request.ErrAuthRequestFailed, // We must set authenticated requests properly in order to understand and better handle auth failures
|
||||
order.ErrUnsupportedOrderType, // Should be returned if an ordertype like ANY is requested and the implementation knows to throw this specific error
|
||||
currency.ErrCurrencyPairEmpty, // Demonstrates handling of EMPTYPAIR scenario and returns the correct error
|
||||
currency.ErrCurrencyNotSupported, // Ensures a standard error is used for when a particular currency/pair is not supported by an exchange
|
||||
currency.ErrCurrencyNotFound, // Semi-randomly selected currency pairs may not be found at an endpoint, so long as this is returned it is okay
|
||||
asset.ErrNotEnabled, // Allows distinction when checking for supported versus enabled
|
||||
request.ErrRateLimiterAlreadyEnabled, // If the rate limiter is already enabled, it is not an error
|
||||
context.DeadlineExceeded, // If the context deadline is exceeded, it is not an error as only blockedCIExchanges use expired contexts by design
|
||||
order.ErrPairIsEmpty, // Is thrown when the empty pair and asset scenario for an order submission is sent in the Validate() function
|
||||
deposit.ErrAddressNotFound, // Is thrown when an address is not found due to the exchange requiring valid API keys
|
||||
}
|
||||
|
||||
// warningErrors will t.Log(err) when thrown to diagnose things, but not necessarily suggest
|
||||
// that the implementation is in error
|
||||
var warningErrors = []error{
|
||||
kline.ErrNoTimeSeriesDataToConvert, // No data returned for a candle isn't worth failing the test suite over necessarily
|
||||
}
|
||||
|
||||
// getPairFromPairs prioritises more normal pairs for an increased
|
||||
// likelihood of returning data from API endpoints
|
||||
func getPairFromPairs(t *testing.T, p currency.Pairs) (currency.Pair, error) {
|
||||
t.Helper()
|
||||
for i := range p {
|
||||
if p[i].Base.Equal(currency.ETH) {
|
||||
return p[i], nil
|
||||
}
|
||||
}
|
||||
for i := range p {
|
||||
if p[i].Base.Equal(currency.BTC) {
|
||||
return p[i], nil
|
||||
}
|
||||
}
|
||||
return p.GetRandomPair()
|
||||
}
|
||||
|
||||
// isFiat helps determine fiat currency without using currency.storage
|
||||
func isFiat(t *testing.T, c string) bool {
|
||||
t.Helper()
|
||||
var fiats = []string{
|
||||
currency.USD.Item.Lower,
|
||||
currency.AUD.Item.Lower,
|
||||
currency.EUR.Item.Lower,
|
||||
currency.CAD.Item.Lower,
|
||||
currency.TRY.Item.Lower,
|
||||
currency.UAH.Item.Lower,
|
||||
currency.RUB.Item.Lower,
|
||||
currency.RUR.Item.Lower,
|
||||
currency.JPY.Item.Lower,
|
||||
currency.HKD.Item.Lower,
|
||||
currency.SGD.Item.Lower,
|
||||
currency.ZUSD.Item.Lower,
|
||||
currency.ZEUR.Item.Lower,
|
||||
currency.ZCAD.Item.Lower,
|
||||
currency.ZJPY.Item.Lower,
|
||||
}
|
||||
for i := range fiats {
|
||||
if fiats[i] == c {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// disruptFormatting adds in an unused delimiter and strange casing features to
|
||||
// ensure format currency pair is used throughout the code base.
|
||||
func disruptFormatting(t *testing.T, p currency.Pair) (currency.Pair, error) {
|
||||
t.Helper()
|
||||
if p.Base.IsEmpty() {
|
||||
return currency.EMPTYPAIR, errors.New("cannot disrupt formatting as base is not populated")
|
||||
}
|
||||
// NOTE: Quote can be empty for margin funding
|
||||
return currency.Pair{
|
||||
Base: p.Base.Upper(),
|
||||
Quote: p.Quote.Lower(),
|
||||
Delimiter: "-TEST-DELIM-",
|
||||
}, nil
|
||||
}
|
||||
|
||||
func getExchangeCredentials(exchangeName string) config.APICredentialsConfig {
|
||||
var resp config.APICredentialsConfig
|
||||
switch exchangeName {
|
||||
case "lbank":
|
||||
// these are just random keys, they are not usable
|
||||
resp.Key = `MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA3R2vuz3cpQUbCX0TgYZL
|
||||
TiLSxUXdrvVEIyoqQyxNf+9fmHLEBrsO1s1msIKvWg24gdbLWXQ6NBCygO8OvZpm
|
||||
+lfXD4MRv/0PxxIAkaD6Iplhv+qbae8nJkYQOpDJF3bPC9LCKfchCnRpZoGqkHgS
|
||||
GqOBU13UDZ8BM1SaOLVBzcmE/iJCLPQPORNSzfLSb8TC+woe0AcaDmF9KjIzXPd0
|
||||
Slacp1ZgZ+yIi1B5/akwxu6sGzHov6weXj/v9K8nUhL9+oPMd8FNzZ+z3viHY0fm
|
||||
yWiHBywwlh4LgzrjGTUdUk9msjSr2rwjTdCp268A8ECC1fChvhdJfO3lYVj8ltDb
|
||||
OQIDAQAB`
|
||||
resp.Secret = `MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDdHa+7PdylBRsJ
|
||||
fROBhktOItLFRd2u9UQjKipDLE1/71+YcsQGuw7WzWawgq9aDbiB1stZdDo0ELKA
|
||||
7w69mmb6V9cPgxG//Q/HEgCRoPoimWG/6ptp7ycmRhA6kMkXds8L0sIp9yEKdGlm
|
||||
gaqQeBIao4FTXdQNnwEzVJo4tUHNyYT+IkIs9A85E1LN8tJvxML7Ch7QBxoOYX0q
|
||||
MjNc93RKVpynVmBn7IiLUHn9qTDG7qwbMei/rB5eP+/0rydSEv36g8x3wU3Nn7Pe
|
||||
+IdjR+bJaIcHLDCWHguDOuMZNR1ST2ayNKvavCNN0KnbrwDwQILV8KG+F0l87eVh
|
||||
WPyW0Ns5AgMBAAECggEAdBs7hJmWO7yzlsbrsC7BajUU8eue3VkCv2hLqtwfkdcz
|
||||
HkzdLB+bSiWvD25//0yHHv6X5tAGJALEiLl+xwbFnhzz27xaXLLYTxLf45hg4Dwk
|
||||
PO9HTlf6+bj+mpIeVcjYLYAs3nZbDi9UjTP3SUcTUpOavBjf2YstyTNai/55oEF/
|
||||
x+ulzP/OISVhKrk5iiSKgjB4KyFpQnBWyluTmnlNS17/T/k6FkECQFgNpzbUmHTH
|
||||
Yq+s0I9fGXMMvsNnnoJjX6ALe9fkMjY6ijeA45plDeBZp+5J8uGOKV+/iTCNzm5o
|
||||
wrQKPz335+tTZgsDdKLUFA9Rwmkcpn4PShOtnR6aZQKBgQD9tzFlomqt/mSWbHAV
|
||||
Gfjog9snlvgEWBIUjfP5Ow79rbz0cGcL3GAexwKK1dwNmMHDx+fu4uVAIf0dM5aT
|
||||
xfdp/I4OTkxOFcIupu+L4gmz1vY32pFLPQYbp+9oOAMy4thUFb5o/Dsq2g65e2BC
|
||||
+gNALEWxPuhNYbI7c0cu5Y7AJwKBgQDfG1ovhNlETJO+oli25csayRwgm/qll4fH
|
||||
sOnYospQiJ3ka0WjPT6NY8m2anWDp7+/guIwq+xXVF6wQNxNZc+6/MgNJo2R3XG5
|
||||
FKPH5FYgI52Zv6VN1AUhdfInDpKQXQ8vWO6HV+/uJmHeZK2+D6nycN4dL2h7ElK/
|
||||
sCthmNtFnwKBgQDCGdaGpLzspAScOBV/b0FH0Shmn07bM+2RIBCYiaAsXzCB6URM
|
||||
hKpcoW/Ge1pAZK9IcrVzws4URGx6XK9EGl3wDbE4LJqf2nGWc0wsPh+iIEB59pLV
|
||||
drgnjFDR8Jgx4+4QVho4A0/Ytr4xFLxOQSsfez9OHIxoNue+J7E7pY+SXQKBgBTT
|
||||
0tl4x2eO1oQHV8zLKui3OX750K5AtRY5N7tXhxd5iXPXZ8rTXtGILT5wNcQylr3k
|
||||
FAWDJy8H20cM5wP6qyfDjVFc9f5V89XZTWjNshSR/pZpw56+WjRDdHWc8KW1akN7
|
||||
Q9kypl1PC/fc4jNJ9w2A59tFn7VNgpgOdB5KTL31AoGBAN3BIjKXzoOJnVGL3bja
|
||||
SYC2m+JcRn/mVO7I5Hop8GDoWXPFAnPNx1YKSpRLM/EV+ukUJsOV/LTPb7BsXMsJ
|
||||
IY9SZceJS6glsxt+blFxGEpypyv13xW+jeCrPjlxQX2TNbL0KwHqvm1zMnM9bss/
|
||||
Rsd80LrBCVI8ctzrvYRFSugC`
|
||||
default:
|
||||
resp.Key = "realKey"
|
||||
resp.Secret = "YXBpU2VjcmV0" // base64 encoded "apiSecret"
|
||||
resp.ClientID = "realClientID"
|
||||
}
|
||||
return resp
|
||||
}
|
||||
|
||||
func isCITest() bool {
|
||||
ci := os.Getenv("CI")
|
||||
return ci == "true" /* github actions */ || ci == "True" /* appveyor */
|
||||
}
|
||||
|
||||
func isAppVeyor() bool {
|
||||
ci := os.Getenv("APPVEYOR")
|
||||
return ci == "True"
|
||||
}
|
||||
|
||||
func is32BitJob() bool {
|
||||
ci := os.Getenv("GITHUB_JOB")
|
||||
return ci == "backend-32bit"
|
||||
}
|
||||
Reference in New Issue
Block a user