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:
Scott
2023-07-03 11:09:43 +10:00
committed by GitHub
parent ef605a3c19
commit fcc5ad4551
210 changed files with 38548 additions and 6519 deletions

View File

@@ -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{

View File

@@ -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)

View File

@@ -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
}

View File

@@ -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) {

View File

@@ -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
}

View File

@@ -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)

View File

@@ -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])

View File

@@ -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
}

View File

@@ -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},

View File

@@ -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"

View File

@@ -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")

View File

@@ -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
}