mirror of
https://github.com/d0zingcat/gocryptotrader.git
synced 2026-05-14 07:26:47 +00:00
* gateio: Add multi asset websocket support WIP. * meow * Add tests and shenanigans * integrate flushing and for enabling/disabling pairs from rpc shenanigans * some changes * linter: fixes strikes again. * Change name ConnectionAssociation -> ConnectionCandidate for better clarity on purpose. Change connections map to point to candidate to track subscriptions for future dynamic connections holder and drop struct ConnectionDetails. * Add subscription tests (state functional) * glorious:nits + proxy handling * Spelling * linter: fixerino * instead of nil, dont do nil. * clean up nils * cya nils * don't need to set URL or check if its running * stop ping handler routine leak * * Fix bug where reader routine on error that is not a disconnection error but websocket frame error or anything really makes the reader routine return and then connection never cycles and the buffer gets filled. * Handle reconnection via an errors.Is check which is simpler and in that scope allow for quick disconnect reconnect without waiting for connection cycle. * Dial now uses code from DialContext but just calls context.Background() * Don't allow reader to return on parse binary response error. Just output error and return a non nil response * Allow rollback on connect on any error across all connections * fix shadow jutsu * glorious/gk: nitters - adds in ws mock server * linter: fix * fix deadlock on connection as the previous channel had no reader and would hang connection reader for eternity. * gk: nits * Leak issue and edge case * gk: nits * gk: drain brain * glorious: nits * Update exchanges/stream/websocket.go Co-authored-by: Scott <gloriousCode@users.noreply.github.com> * glorious: nits * add tests * linter: fix * After merge * Add error connection info * Fix edge case where it does not reconnect made by an already closed connection * stream coverage * glorious: nits * glorious: nits removed asset error handling in stream package * linter: fix * rm block * Add basic readme * fix asset enabled flush cycle for multi connection * spella: fix * linter: fix * Add glorious suggestions, fix some race thing * reinstate name before any routine gets spawned * stop on error in mock tests * glorious: nits * glorious: nits found in CI build * Add test for drain, bumped wait times as there seems to be something happening on macos CI builds, used context.WithTimeout because its instant. * mutex across shutdown and connect for protection * lint: fix * test time withoffset, reinstate stop * fix whoops * const trafficCheckInterval; rm testmain * y * fix lint * bump time check window * stream: fix intermittant test failures while testing routines and remove code that is not needed. * spells * cant do what I did * protect race due to routine. * update testURL * use mock websocket connection instead of test URL's * linter: fix * remove url because its throwing errors on CI builds * connections drop all the time, don't need to worry about not being able to echo back ws data as it can be easily reviewed _test file side. * remove another superfluous url thats not really set up for this * spawn overwatch routine when there is no errors, inline checker instead of waiting for a time period, add sleep inline with echo handler as this is really quick and wanted to ensure that latency is handing correctly * linter: fixerino uperino * glorious: panix * linter: things * whoops * defer lock and use functions that don't require locking in SetProxyAddress * lint: fix * thrasher: nits --------- Co-authored-by: shazbert <ryan.oharareid@thrasher.io> Co-authored-by: Scott <gloriousCode@users.noreply.github.com>
204 lines
6.4 KiB
Go
204 lines
6.4 KiB
Go
package exchange
|
|
|
|
import (
|
|
"bufio"
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"log"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"sync"
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
"github.com/thrasher-corp/gocryptotrader/config"
|
|
"github.com/thrasher-corp/gocryptotrader/currency"
|
|
exchange "github.com/thrasher-corp/gocryptotrader/exchanges"
|
|
"github.com/thrasher-corp/gocryptotrader/exchanges/mock"
|
|
"github.com/thrasher-corp/gocryptotrader/exchanges/sharedtestvalues"
|
|
"github.com/thrasher-corp/gocryptotrader/exchanges/subscription"
|
|
testutils "github.com/thrasher-corp/gocryptotrader/internal/testing/utils"
|
|
)
|
|
|
|
// Setup takes an empty exchange instance and loads config for it from testdata/configtest and connects a NewTestWebsocket
|
|
func Setup(e exchange.IBotExchange) error {
|
|
cfg := &config.Config{}
|
|
|
|
root, err := testutils.RootPathFromCWD()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
err = cfg.LoadConfig(filepath.Join(root, "testdata", "configtest.json"), true)
|
|
if err != nil {
|
|
return fmt.Errorf("LoadConfig() error: %w", err)
|
|
}
|
|
parts := strings.Split(fmt.Sprintf("%T", e), ".")
|
|
if len(parts) != 2 {
|
|
return errors.New("unexpected parts splitting exchange type name")
|
|
}
|
|
eName := parts[1]
|
|
exchConf, err := cfg.GetExchangeConfig(eName)
|
|
if err != nil {
|
|
return fmt.Errorf("GetExchangeConfig(`%s`) error: %w", eName, err)
|
|
}
|
|
e.SetDefaults()
|
|
b := e.GetBase()
|
|
b.Websocket = sharedtestvalues.NewTestWebsocket()
|
|
err = e.Setup(exchConf)
|
|
if err != nil {
|
|
return fmt.Errorf("Setup() error: %w", err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// httpMockFile is a consistent path under each exchange to find the mock server definitions
|
|
const httpMockFile = "testdata/http.json"
|
|
|
|
// MockHTTPInstance takes an existing Exchange instance and attaches it to a new http server
|
|
// It is expected to be run once, since http requests do not often tangle with each other
|
|
func MockHTTPInstance(e exchange.IBotExchange) error {
|
|
serverDetails, newClient, err := mock.NewVCRServer(httpMockFile)
|
|
if err != nil {
|
|
return fmt.Errorf("mock server error %s", err)
|
|
}
|
|
b := e.GetBase()
|
|
b.SkipAuthCheck = true
|
|
err = b.SetHTTPClient(newClient)
|
|
if err != nil {
|
|
return fmt.Errorf("mock server error %s", err)
|
|
}
|
|
endpointMap := b.API.Endpoints.GetURLMap()
|
|
for k := range endpointMap {
|
|
err = b.API.Endpoints.SetRunning(k, serverDetails)
|
|
if err != nil {
|
|
return fmt.Errorf("mock server error %s", err)
|
|
}
|
|
}
|
|
log.Printf(sharedtestvalues.MockTesting, e.GetName())
|
|
|
|
return nil
|
|
}
|
|
|
|
// MockWsInstance creates a new Exchange instance with a mock websocket instance and HTTP server
|
|
// It accepts an exchange package type argument and a http.HandlerFunc
|
|
// See CurryWsMockUpgrader for a convenient way to curry t and a ws mock function
|
|
// It is expected to be run from any WS tests which need a specific response function
|
|
// No default subscriptions will be run since they disrupt unit tests
|
|
func MockWsInstance[T any, PT interface {
|
|
*T
|
|
exchange.IBotExchange
|
|
}](tb testing.TB, h http.HandlerFunc) *T {
|
|
tb.Helper()
|
|
|
|
e := PT(new(T))
|
|
require.NoError(tb, Setup(e), "Test exchange Setup must not error")
|
|
|
|
s := httptest.NewServer(h)
|
|
|
|
b := e.GetBase()
|
|
b.SkipAuthCheck = true
|
|
b.API.AuthenticatedWebsocketSupport = true
|
|
err := b.API.Endpoints.SetRunning("RestSpotURL", s.URL)
|
|
require.NoError(tb, err, "Endpoints.SetRunning should not error for RestSpotURL")
|
|
for _, auth := range []bool{true, false} {
|
|
err = b.Websocket.SetWebsocketURL("ws"+strings.TrimPrefix(s.URL, "http"), auth, true)
|
|
require.NoErrorf(tb, err, "SetWebsocketURL should not error for auth: %v", auth)
|
|
}
|
|
|
|
// For testing we never want to use the default subscriptions; Tests of GenerateSubscriptions should be exercising it directly
|
|
b.Features.Subscriptions = subscription.List{}
|
|
// Exchanges which don't support subscription conf; Can be removed when all exchanges support sub conf
|
|
b.Websocket.GenerateSubs = func() (subscription.List, error) { return subscription.List{}, nil }
|
|
|
|
err = b.Websocket.Connect()
|
|
require.NoError(tb, err, "Connect should not error")
|
|
|
|
return e
|
|
}
|
|
|
|
// FixtureToDataHandler squirts the contents of a file to a reader function (probably e.wsHandleData)
|
|
func FixtureToDataHandler(tb testing.TB, fixturePath string, reader func([]byte) error) {
|
|
tb.Helper()
|
|
|
|
fixture, err := os.Open(fixturePath)
|
|
assert.NoError(tb, err, "Opening fixture '%s' should not error", fixturePath)
|
|
defer func() {
|
|
assert.NoError(tb, fixture.Close(), "Closing the fixture file should not error")
|
|
}()
|
|
|
|
s := bufio.NewScanner(fixture)
|
|
for s.Scan() {
|
|
msg := s.Bytes()
|
|
err := reader(msg)
|
|
assert.NoErrorf(tb, err, "Fixture message should not error:\n%s", msg)
|
|
}
|
|
assert.NoError(tb, s.Err(), "Fixture Scanner should not error")
|
|
}
|
|
|
|
var setupWsMutex sync.Mutex
|
|
var setupWsOnce = make(map[exchange.IBotExchange]bool)
|
|
|
|
// SetupWs is a helper function to connect both auth and normal websockets
|
|
// It will skip the test if websockets are not enabled
|
|
// It's up to the test to skip if it requires creds, though
|
|
func SetupWs(tb testing.TB, e exchange.IBotExchange) {
|
|
tb.Helper()
|
|
|
|
setupWsMutex.Lock()
|
|
defer setupWsMutex.Unlock()
|
|
|
|
if setupWsOnce[e] {
|
|
return
|
|
}
|
|
|
|
b := e.GetBase()
|
|
w, err := b.GetWebsocket()
|
|
if err != nil || !b.Websocket.IsEnabled() {
|
|
tb.Skip("Websocket not enabled")
|
|
}
|
|
if w.IsConnected() {
|
|
return
|
|
}
|
|
|
|
// For testing we never want to use the default subscriptions; Tests of GenerateSubscriptions should be exercising it directly
|
|
b.Features.Subscriptions = subscription.List{}
|
|
// Exchanges which don't support subscription conf; Can be removed when all exchanges support sub conf
|
|
w.GenerateSubs = func() (subscription.List, error) { return subscription.List{}, nil }
|
|
|
|
err = w.Connect()
|
|
require.NoError(tb, err, "WsConnect should not error")
|
|
|
|
setupWsOnce[e] = true
|
|
}
|
|
|
|
var updatePairsMutex sync.Mutex
|
|
var updatePairsOnce = make(map[string]*currency.PairsManager)
|
|
|
|
// UpdatePairsOnce ensures pairs are only updated once in parallel tests
|
|
// A clone of the cache of the updated pairs is used to populate duplicate requests
|
|
func UpdatePairsOnce(tb testing.TB, e exchange.IBotExchange) {
|
|
tb.Helper()
|
|
|
|
updatePairsMutex.Lock()
|
|
defer updatePairsMutex.Unlock()
|
|
|
|
b := e.GetBase()
|
|
if c, ok := updatePairsOnce[e.GetName()]; ok {
|
|
b.CurrencyPairs.Load(c)
|
|
return
|
|
}
|
|
|
|
err := e.UpdateTradablePairs(context.Background(), true)
|
|
require.NoError(tb, err, "UpdateTradablePairs must not error")
|
|
|
|
cache := new(currency.PairsManager)
|
|
cache.Load(&b.CurrencyPairs)
|
|
updatePairsOnce[e.GetName()] = cache
|
|
}
|