Tests: Various race fixes and move TestFixtureToDataHandler (#1534)

* Tests: Move and simplify TestFixtureToDataHandler

* Currency: Fix PairsManager.Load breaking matcher

* Tests: Add multi-instance cache to UpdatePairsOnce

* Kraken: Fix TestUpdateTickers race error

Calling StorePairs on global instance can lead to race

* Bitfinex: Fix TestUpdateTickers racing intermittently

* Currency: Fix concurrent access to PM formats

* Currency: Fix SupportsAsset implementation

This should delegate entirely to PairManager's IsAssetSupported

* Okx: Fix PM intrusion, rm GetPairFromInstrumentID

* Exchange: Fix SetGlobalPairsManager to set asset enabled

* Bitflyer: Fix race on set TestGetCurrURL

TestGetCurrencyTradeURL would fail sometimes due to sequencing of
enabling futures but not having pairs for it.

* Tests: Simplify usage pattern for FixtureToDH
This commit is contained in:
Gareth Kirwan
2024-05-16 06:09:26 +02:00
committed by GitHub
parent 34ef09dad6
commit 7d1eecfa7e
19 changed files with 584 additions and 693 deletions

View File

@@ -547,6 +547,8 @@ func TestUpdateTicker(t *testing.T) {
func TestUpdateTickers(t *testing.T) {
t.Parallel()
b := new(Bitfinex) //nolint:govet // Intentional shadow to avoid future copy/paste mistakes
require.NoError(t, testexch.Setup(b), "Test instance Setup must not error")
testexch.UpdatePairsOnce(t, b)
assets := b.GetAssetTypes(false)

View File

@@ -455,14 +455,14 @@ func TestUpdateTradablePairs(t *testing.T) {
func TestGetCurrencyTradeURL(t *testing.T) {
t.Parallel()
testexch.UpdatePairsOnce(t, b)
err := b.CurrencyPairs.SetAssetEnabled(asset.Futures, true)
require.NoError(t, err)
err := b.CurrencyPairs.SetAssetEnabled(asset.Futures, false)
require.NoError(t, err, "SetAssetEnabled must not error")
for _, a := range b.GetAssetTypes(false) {
pairs, err := b.CurrencyPairs.GetPairs(a, false)
require.NoError(t, err, "cannot get pairs for %s", a)
require.NotEmpty(t, pairs, "no pairs for %s", a)
resp, err := b.GetCurrencyTradeURL(context.Background(), a, pairs[0])
require.NoError(t, err)
assert.NotEmpty(t, resp)
require.NoError(t, err, "GetCurrencyTradeURL must not error")
assert.NotEmpty(t, resp, "GetCurrencyTradeURL should return an url")
}
}

View File

@@ -776,84 +776,79 @@ func TestWsOrderbook2(t *testing.T) {
func TestWsOrderUpdate(t *testing.T) {
t.Parallel()
n := new(Bitstamp)
sharedtestvalues.TestFixtureToDataHandler(t, b, n, "testdata/wsMyOrders.json", n.wsHandleData)
seen := 0
for reading := true; reading; {
select {
default:
reading = false
case resp := <-n.GetBase().Websocket.DataHandler:
seen++
switch v := resp.(type) {
case *order.Detail:
switch seen {
case 1:
assert.Equal(t, "1658864794234880", v.OrderID, "OrderID")
assert.Equal(t, time.UnixMicro(1693831262313000), v.Date, "Date")
assert.Equal(t, "test_market_buy", v.ClientOrderID, "ClientOrderID")
assert.Equal(t, order.New, v.Status, "Status")
assert.Equal(t, order.Buy, v.Side, "Side")
assert.Equal(t, asset.Spot, v.AssetType, "AssetType")
assert.Equal(t, currency.NewPairWithDelimiter("BTC", "USD", "/"), v.Pair, "Pair")
assert.Equal(t, 0.0, v.ExecutedAmount, "ExecutedAmount")
assert.Equal(t, 999999999.0, v.Price, "Price") // Market Buy Price
// Note: Amount is 0 for market order create messages, oddly
case 2:
assert.Equal(t, "1658864794234880", v.OrderID, "OrderID")
assert.Equal(t, order.PartiallyFilled, v.Status, "Status")
assert.Equal(t, 0.00038667, v.Amount, "Amount")
assert.Equal(t, 0.00000001, v.RemainingAmount, "RemainingAmount") // During live tests we consistently got back this Sat remaining
assert.Equal(t, 0.00038666, v.ExecutedAmount, "ExecutedAmount")
assert.Equal(t, 25862.0, v.Price, "Price")
case 3:
assert.Equal(t, "1658864794234880", v.OrderID, "OrderID")
assert.Equal(t, order.Cancelled, v.Status, "Status") // Even though they probably consider it filled, Deleted + PartialFill = Cancelled
assert.Equal(t, 0.00038667, v.Amount, "Amount")
assert.Equal(t, 0.00000001, v.RemainingAmount, "RemainingAmount")
assert.Equal(t, 0.00038666, v.ExecutedAmount, "ExecutedAmount")
assert.Equal(t, 25862.0, v.Price, "Price")
case 4:
assert.Equal(t, "1658870500933632", v.OrderID, "OrderID")
assert.Equal(t, order.New, v.Status, "Status")
assert.Equal(t, order.Sell, v.Side, "Side")
assert.Equal(t, 0.0, v.Price, "Price") // Market Sell Price
case 5:
assert.Equal(t, "1658870500933632", v.OrderID, "OrderID")
assert.Equal(t, order.PartiallyFilled, v.Status, "Status")
assert.Equal(t, 0.00038679, v.Amount, "Amount")
assert.Equal(t, 0.00000001, v.RemainingAmount, "RemainingAmount")
assert.Equal(t, 0.00038678, v.ExecutedAmount, "ExecutedAmount")
assert.Equal(t, 25854.0, v.Price, "Price")
case 6:
assert.Equal(t, "1658870500933632", v.OrderID, "OrderID")
assert.Equal(t, order.Cancelled, v.Status, "Status")
assert.Equal(t, 0.00038679, v.Amount, "Amount")
assert.Equal(t, 0.00000001, v.RemainingAmount, "RemainingAmount")
assert.Equal(t, 0.00038678, v.ExecutedAmount, "ExecutedAmount")
assert.Equal(t, 25854.0, v.Price, "Price")
case 7:
assert.Equal(t, "1658869033291777", v.OrderID, "OrderID")
assert.Equal(t, order.New, v.Status, "Status")
assert.Equal(t, order.Sell, v.Side, "Side")
assert.Equal(t, 25845.0, v.Price, "Price")
assert.Equal(t, 0.00038692, v.Amount, "Amount")
case 8:
assert.Equal(t, "1658869033291777", v.OrderID, "OrderID")
assert.Equal(t, order.Filled, v.Status, "Status")
assert.Equal(t, 25845.0, v.Price, "Price")
assert.Equal(t, 0.00038692, v.Amount, "Amount")
assert.Equal(t, 0.0, v.RemainingAmount, "RemainingAmount")
assert.Equal(t, 0.00038692, v.ExecutedAmount, "ExecutedAmount")
}
case error:
t.Error(v)
default:
t.Errorf("Got unexpected data: %T %v", v, v)
b := new(Bitstamp) //nolint:govet // Intentional shadow to avoid future copy/paste mistakes
require.NoError(t, testexch.Setup(b), "Test instance Setup must not error")
testexch.FixtureToDataHandler(t, "testdata/wsMyOrders.json", b.wsHandleData)
close(b.Websocket.DataHandler)
assert.Len(t, b.Websocket.DataHandler, 8, "Should see 8 orders")
for resp := range b.Websocket.DataHandler {
switch v := resp.(type) {
case *order.Detail:
switch len(b.Websocket.DataHandler) {
case 7:
assert.Equal(t, "1658864794234880", v.OrderID, "OrderID")
assert.Equal(t, time.UnixMicro(1693831262313000), v.Date, "Date")
assert.Equal(t, "test_market_buy", v.ClientOrderID, "ClientOrderID")
assert.Equal(t, order.New, v.Status, "Status")
assert.Equal(t, order.Buy, v.Side, "Side")
assert.Equal(t, asset.Spot, v.AssetType, "AssetType")
assert.Equal(t, currency.NewPairWithDelimiter("BTC", "USD", "/"), v.Pair, "Pair")
assert.Equal(t, 0.0, v.ExecutedAmount, "ExecutedAmount")
assert.Equal(t, 999999999.0, v.Price, "Price") // Market Buy Price
// Note: Amount is 0 for market order create messages, oddly
case 6:
assert.Equal(t, "1658864794234880", v.OrderID, "OrderID")
assert.Equal(t, order.PartiallyFilled, v.Status, "Status")
assert.Equal(t, 0.00038667, v.Amount, "Amount")
assert.Equal(t, 0.00000001, v.RemainingAmount, "RemainingAmount") // During live tests we consistently got back this Sat remaining
assert.Equal(t, 0.00038666, v.ExecutedAmount, "ExecutedAmount")
assert.Equal(t, 25862.0, v.Price, "Price")
case 5:
assert.Equal(t, "1658864794234880", v.OrderID, "OrderID")
assert.Equal(t, order.Cancelled, v.Status, "Status") // Even though they probably consider it filled, Deleted + PartialFill = Cancelled
assert.Equal(t, 0.00038667, v.Amount, "Amount")
assert.Equal(t, 0.00000001, v.RemainingAmount, "RemainingAmount")
assert.Equal(t, 0.00038666, v.ExecutedAmount, "ExecutedAmount")
assert.Equal(t, 25862.0, v.Price, "Price")
case 4:
assert.Equal(t, "1658870500933632", v.OrderID, "OrderID")
assert.Equal(t, order.New, v.Status, "Status")
assert.Equal(t, order.Sell, v.Side, "Side")
assert.Equal(t, 0.0, v.Price, "Price") // Market Sell Price
case 3:
assert.Equal(t, "1658870500933632", v.OrderID, "OrderID")
assert.Equal(t, order.PartiallyFilled, v.Status, "Status")
assert.Equal(t, 0.00038679, v.Amount, "Amount")
assert.Equal(t, 0.00000001, v.RemainingAmount, "RemainingAmount")
assert.Equal(t, 0.00038678, v.ExecutedAmount, "ExecutedAmount")
assert.Equal(t, 25854.0, v.Price, "Price")
case 2:
assert.Equal(t, "1658870500933632", v.OrderID, "OrderID")
assert.Equal(t, order.Cancelled, v.Status, "Status")
assert.Equal(t, 0.00038679, v.Amount, "Amount")
assert.Equal(t, 0.00000001, v.RemainingAmount, "RemainingAmount")
assert.Equal(t, 0.00038678, v.ExecutedAmount, "ExecutedAmount")
assert.Equal(t, 25854.0, v.Price, "Price")
case 1:
assert.Equal(t, "1658869033291777", v.OrderID, "OrderID")
assert.Equal(t, order.New, v.Status, "Status")
assert.Equal(t, order.Sell, v.Side, "Side")
assert.Equal(t, 25845.0, v.Price, "Price")
assert.Equal(t, 0.00038692, v.Amount, "Amount")
case 0:
assert.Equal(t, "1658869033291777", v.OrderID, "OrderID")
assert.Equal(t, order.Filled, v.Status, "Status")
assert.Equal(t, 25845.0, v.Price, "Price")
assert.Equal(t, 0.00038692, v.Amount, "Amount")
assert.Equal(t, 0.0, v.RemainingAmount, "RemainingAmount")
assert.Equal(t, 0.00038692, v.ExecutedAmount, "ExecutedAmount")
}
case error:
t.Error(v)
default:
t.Errorf("Got unexpected data: %T %v", v, v)
}
}
assert.Equal(t, 8, seen, "Number of messages")
}
func TestWsRequestReconnect(t *testing.T) {

View File

@@ -55,10 +55,6 @@ var (
errEndpointStringNotFound = errors.New("endpoint string not found")
errConfigPairFormatRequiresDelimiter = errors.New("config pair format requires delimiter")
errSymbolCannotBeMatched = errors.New("symbol cannot be matched")
errGlobalRequestFormatIsNil = errors.New("global request format is nil")
errGlobalConfigFormatIsNil = errors.New("global config format is nil")
errAssetRequestFormatIsNil = errors.New("asset type request format is nil")
errAssetConfigFormatIsNil = errors.New("asset type config format is nil")
errSetDefaultsNotCalled = errors.New("set defaults not called")
errExchangeIsNil = errors.New("exchange is nil")
errBatchSizeZero = errors.New("batch size cannot be 0")
@@ -397,39 +393,9 @@ func (b *Base) GetSupportedFeatures() FeaturesSupported {
return b.Features.Supports
}
// GetPairFormat returns the pair format based on the exchange and
// asset type
func (b *Base) GetPairFormat(assetType asset.Item, requestFormat bool) (currency.PairFormat, error) {
if b.CurrencyPairs.UseGlobalFormat {
if requestFormat {
if b.CurrencyPairs.RequestFormat == nil {
return currency.EMPTYFORMAT, errGlobalRequestFormatIsNil
}
return *b.CurrencyPairs.RequestFormat, nil
}
if b.CurrencyPairs.ConfigFormat == nil {
return currency.EMPTYFORMAT, errGlobalConfigFormatIsNil
}
return *b.CurrencyPairs.ConfigFormat, nil
}
ps, err := b.CurrencyPairs.Get(assetType)
if err != nil {
return currency.EMPTYFORMAT, err
}
if requestFormat {
if ps.RequestFormat == nil {
return currency.EMPTYFORMAT, errAssetRequestFormatIsNil
}
return *ps.RequestFormat, nil
}
if ps.ConfigFormat == nil {
return currency.EMPTYFORMAT, errAssetConfigFormatIsNil
}
return *ps.ConfigFormat, nil
// GetPairFormat returns the pair format based on the exchange and asset type
func (b *Base) GetPairFormat(a asset.Item, r bool) (currency.PairFormat, error) {
return b.CurrencyPairs.GetFormat(a, r)
}
// GetEnabledPairs is a method that returns the enabled currency pairs of
@@ -1007,11 +973,9 @@ func (b *Base) FormatWithdrawPermissions() string {
return NoAPIWithdrawalMethodsText
}
// SupportsAsset whether or not the supplied asset is supported
// by the exchange
// SupportsAsset whether or not the supplied asset is supported by the exchange
func (b *Base) SupportsAsset(a asset.Item) bool {
_, ok := b.CurrencyPairs.Pairs[a]
return ok
return b.CurrencyPairs.IsAssetSupported(a)
}
// PrintEnabledPairs prints the exchanges enabled asset pairs
@@ -1082,12 +1046,10 @@ func (b *Base) StoreAssetPairFormat(a asset.Item, f currency.PairStore) error {
return nil
}
// SetGlobalPairsManager sets defined asset and pairs management system with
// global formatting
// SetGlobalPairsManager sets defined asset and pairs management system with global formatting
func (b *Base) SetGlobalPairsManager(request, config *currency.PairFormat, assets ...asset.Item) error {
if request == nil {
return fmt.Errorf("%s cannot set pairs manager, request pair format not provided",
b.Name)
return fmt.Errorf("%s cannot set pairs manager, request pair format not provided", b.Name)
}
if config == nil {
@@ -1119,10 +1081,10 @@ func (b *Base) SetGlobalPairsManager(request, config *currency.PairFormat, asset
for i := range assets {
if assets[i].String() == "" {
b.CurrencyPairs.Pairs = nil
return fmt.Errorf("%s cannot set pairs manager, asset is empty string",
b.Name)
return fmt.Errorf("%s cannot set pairs manager, asset is empty string", b.Name)
}
b.CurrencyPairs.Pairs[assets[i]] = new(currency.PairStore)
b.CurrencyPairs.Pairs[assets[i]].AssetEnabled = convert.BoolPtr(true)
b.CurrencyPairs.Pairs[assets[i]].ConfigFormat = config
b.CurrencyPairs.Pairs[assets[i]].RequestFormat = request
}

View File

@@ -696,56 +696,12 @@ func TestGetFeatures(t *testing.T) {
}
}
// TestGetPairFormat ensures that GetPairFormat delegates to PairsManager.GetFormat
func TestGetPairFormat(t *testing.T) {
t.Parallel()
// Test global formatting
var b Base
b.CurrencyPairs.UseGlobalFormat = true
b.CurrencyPairs.ConfigFormat = &currency.PairFormat{
Uppercase: true,
}
b.CurrencyPairs.RequestFormat = &currency.PairFormat{
Delimiter: "~",
}
pFmt, err := b.GetPairFormat(asset.Spot, true)
if err != nil {
t.Fatal(err)
}
if pFmt.Delimiter != "~" && !pFmt.Uppercase {
t.Error("incorrect pair format values")
}
pFmt, err = b.GetPairFormat(asset.Spot, false)
if err != nil {
t.Fatal(err)
}
if pFmt.Delimiter != "" && pFmt.Uppercase {
t.Error("incorrect pair format values")
}
// Test individual asset pair store formatting
b.CurrencyPairs.UseGlobalFormat = false
err = b.CurrencyPairs.Store(asset.Spot, &currency.PairStore{
ConfigFormat: &pFmt,
RequestFormat: &currency.PairFormat{Delimiter: "/", Uppercase: true},
})
if err != nil {
t.Fatal(err)
}
pFmt, err = b.GetPairFormat(asset.Spot, false)
if err != nil {
t.Fatal(err)
}
if pFmt.Delimiter != "" && pFmt.Uppercase {
t.Error("incorrect pair format values")
}
pFmt, err = b.GetPairFormat(asset.Spot, true)
if err != nil {
t.Fatal(err)
}
if pFmt.Delimiter != "~" && !pFmt.Uppercase {
t.Error("incorrect pair format values")
}
_, err := new(Base).GetPairFormat(asset.Spot, true)
require.ErrorIs(t, err, asset.ErrNotSupported, "Must delegate to GetFormat and error")
}
func TestGetPairs(t *testing.T) {
@@ -778,6 +734,7 @@ func TestGetPairs(t *testing.T) {
}
}
// TestFormatExchangeCurrencies exercises FormatExchangeCurrencies
func TestFormatExchangeCurrencies(t *testing.T) {
t.Parallel()
@@ -797,32 +754,18 @@ func TestFormatExchangeCurrencies(t *testing.T) {
},
},
}
p1, err := currency.NewPairDelimiter("BTC_USD", "_")
if err != nil {
t.Fatal(err)
}
p2, err := currency.NewPairDelimiter("LTC_BTC", "_")
if err != nil {
t.Fatal(err)
}
var pairs = []currency.Pair{
p1,
p2,
currency.NewPairWithDelimiter("BTC", "USD", "_"),
currency.NewPairWithDelimiter("LTC", "BTC", "_"),
}
actual, err := e.FormatExchangeCurrencies(pairs, asset.Spot)
if err != nil {
t.Errorf("Exchange TestFormatExchangeCurrencies error %s", err)
}
if expected := "btc~usd^ltc~btc"; actual != expected {
t.Errorf("Exchange TestFormatExchangeCurrencies %s != %s",
actual, expected)
}
got, err := e.FormatExchangeCurrencies(pairs, asset.Spot)
require.NoError(t, err)
assert.Equal(t, "btc~usd^ltc~btc", got)
_, err = e.FormatExchangeCurrencies(nil, asset.Spot)
if err == nil {
t.Error("nil pairs should return an error")
}
assert.ErrorContains(t, err, "returned empty string", err, "FormatExchangeCurrencies should error correctly")
}
func TestFormatExchangeCurrency(t *testing.T) {
@@ -1342,14 +1285,12 @@ func TestSupportsAsset(t *testing.T) {
t.Parallel()
var b Base
b.CurrencyPairs.Pairs = map[asset.Item]*currency.PairStore{
asset.Spot: {},
}
if !b.SupportsAsset(asset.Spot) {
t.Error("spot should be supported")
}
if b.SupportsAsset(asset.Index) {
t.Error("index shouldn't be supported")
asset.Spot: {
AssetEnabled: convert.BoolPtr(true),
},
}
assert.True(t, b.SupportsAsset(asset.Spot), "Spot should be supported")
assert.False(t, b.SupportsAsset(asset.Index), "Index should not be supported")
}
func TestPrintEnabledPairs(t *testing.T) {
@@ -1494,58 +1435,36 @@ func TestStoreAssetPairFormat(t *testing.T) {
}
func TestSetGlobalPairsManager(t *testing.T) {
b := Base{
Config: &config.Exchange{Name: "kitties"},
}
b := Base{Config: &config.Exchange{Name: "kitties"}}
err := b.SetGlobalPairsManager(nil, nil, asset.Empty)
if err == nil {
t.Error("error cannot be nil")
}
assert.ErrorContains(t, err, "cannot set pairs manager, request pair format not provided")
err = b.SetGlobalPairsManager(&currency.PairFormat{Uppercase: true}, nil, asset.Empty)
if err == nil {
t.Error("error cannot be nil")
}
assert.ErrorContains(t, err, "cannot set pairs manager, config pair format not provided")
err = b.SetGlobalPairsManager(&currency.PairFormat{Uppercase: true},
&currency.PairFormat{Uppercase: true})
if err == nil {
t.Error("error cannot be nil")
}
err = b.SetGlobalPairsManager(&currency.PairFormat{Uppercase: true}, &currency.PairFormat{Uppercase: true})
assert.ErrorContains(t, err, " cannot set pairs manager, no assets provided")
err = b.SetGlobalPairsManager(&currency.PairFormat{Uppercase: true},
&currency.PairFormat{Uppercase: true}, asset.Empty)
if err == nil {
t.Error("error cannot be nil")
}
err = b.SetGlobalPairsManager(&currency.PairFormat{Uppercase: true}, &currency.PairFormat{Uppercase: true}, asset.Empty)
assert.ErrorContains(t, err, " cannot set global pairs manager config pair format requires delimiter for assets")
err = b.SetGlobalPairsManager(&currency.PairFormat{Uppercase: true},
&currency.PairFormat{Uppercase: true},
asset.Spot,
asset.Binary)
if !errors.Is(err, errConfigPairFormatRequiresDelimiter) {
t.Fatalf("received: '%v' but expected: '%v'", err, errConfigPairFormatRequiresDelimiter)
}
assert.ErrorIs(t, err, errConfigPairFormatRequiresDelimiter)
err = b.SetGlobalPairsManager(&currency.PairFormat{Uppercase: true},
&currency.PairFormat{Uppercase: true, Delimiter: currency.DashDelimiter},
asset.Spot,
asset.Binary)
if err != nil {
t.Error(err)
}
err = b.SetGlobalPairsManager(&currency.PairFormat{Uppercase: true}, &currency.PairFormat{Uppercase: true, Delimiter: currency.DashDelimiter}, asset.Spot, asset.Binary)
require.NoError(t, err, "SetGlobalPairsManager must not error")
if !b.SupportsAsset(asset.Binary) || !b.SupportsAsset(asset.Spot) {
t.Fatal("global pairs manager not set correctly")
}
assert.True(t, b.SupportsAsset(asset.Binary), "Pairs Manager must support Binary")
assert.True(t, b.SupportsAsset(asset.Spot), "Pairs Manager must support Spot")
err = b.SetGlobalPairsManager(&currency.PairFormat{Uppercase: true},
&currency.PairFormat{Uppercase: true}, asset.Spot, asset.Binary)
if err == nil {
t.Error("error cannot be nil")
}
err = b.SetGlobalPairsManager(&currency.PairFormat{Uppercase: true}, &currency.PairFormat{Uppercase: true}, asset.Spot, asset.Binary)
assert.ErrorIs(t, err, errConfigPairFormatRequiresDelimiter, "SetGlobalPairsManager should error correctly")
}
func Test_FormatExchangeKlineInterval(t *testing.T) {
testCases := []struct {
name string
@@ -2366,9 +2285,7 @@ func TestGetKlineRequest(t *testing.T) {
b.Features.Enabled.Kline.Intervals = kline.DeployExchangeIntervals(kline.IntervalCapacity{Interval: kline.OneMin})
b.Features.Enabled.Kline.GlobalResultLimit = 1439
_, err = b.GetKlineRequest(pair, asset.Spot, kline.OneHour, time.Time{}, time.Time{}, false)
if !errors.Is(err, errAssetRequestFormatIsNil) {
t.Fatalf("received: '%v' but expected: '%v'", err, errAssetRequestFormatIsNil)
}
assert.ErrorIs(t, err, currency.ErrPairFormatIsNil, "GetKlineRequest should return Format is Nil")
err = b.CurrencyPairs.Store(asset.Spot, &currency.PairStore{
AssetEnabled: convert.BoolPtr(true),
@@ -2533,9 +2450,7 @@ func TestGetKlineExtendedRequest(t *testing.T) {
}
_, err = b.GetKlineExtendedRequest(pair, asset.Spot, kline.OneHour, start, end)
if !errors.Is(err, errAssetRequestFormatIsNil) {
t.Fatalf("received: '%v' but expected: '%v'", err, errAssetRequestFormatIsNil)
}
assert.ErrorIs(t, err, currency.ErrPairFormatIsNil, "GetKlineExtendedRequest should error correctly")
err = b.CurrencyPairs.Store(asset.Spot, &currency.PairStore{
AssetEnabled: convert.BoolPtr(true),

View File

@@ -3753,8 +3753,12 @@ func (g *Gateio) IsValidPairString(currencyPair string) bool {
if len(currencyPair) < 3 {
return false
}
if strings.Contains(currencyPair, g.CurrencyPairs.RequestFormat.Delimiter) {
result := strings.Split(currencyPair, g.CurrencyPairs.RequestFormat.Delimiter)
pf, err := g.CurrencyPairs.GetFormat(asset.Spot, true)
if err != nil {
return false
}
if strings.Contains(currencyPair, pf.Delimiter) {
result := strings.Split(currencyPair, pf.Delimiter)
return len(result) >= 2
}
return false

View File

@@ -130,31 +130,28 @@ func TestUpdateTicker(t *testing.T) {
func TestUpdateTickers(t *testing.T) {
t.Parallel()
k := new(Kraken) //nolint:govet // Intentional shadow to avoid future copy/paste mistakes
require.NoError(t, testexch.Setup(k), "Test instance Setup must not error")
testexch.UpdatePairsOnce(t, k)
err := k.UpdateTickers(context.Background(), asset.Spot)
require.NoError(t, err, "UpdateTickers must not error")
ap, err := k.GetAvailablePairs(asset.Spot)
require.NoError(t, err)
err = k.CurrencyPairs.StorePairs(asset.Spot, ap, true)
require.NoError(t, err)
err = k.UpdateTickers(context.Background(), asset.Spot)
assert.NoError(t, err)
require.NoError(t, err, "GetAvailablePairs must not error")
for i := range ap {
_, err = ticker.GetTicker(k.Name, ap[i], asset.Spot)
assert.NoError(t, err)
require.NoError(t, err, "GetTicker must not error")
}
ap, err = k.GetAvailablePairs(asset.Futures)
require.NoError(t, err)
err = k.CurrencyPairs.StorePairs(asset.Futures, ap, true)
require.NoError(t, err)
require.NoError(t, err, "GetAvailablePairs must not error")
err = k.UpdateTickers(context.Background(), asset.Futures)
assert.NoError(t, err)
require.NoError(t, err, "UpdateTickers must not error")
for i := range ap {
_, err = ticker.GetTicker(k.Name, ap[i], asset.Futures)
assert.NoError(t, err)
require.NoError(t, err, "GetTicker must not error")
}
err = k.UpdateTickers(context.Background(), asset.Index)
assert.ErrorIs(t, err, asset.ErrNotSupported)
assert.ErrorIs(t, err, asset.ErrNotSupported, "UpdateTickers should error correctly on Index asset")
}
func TestUpdateOrderbook(t *testing.T) {
@@ -1827,73 +1824,68 @@ func TestWsOwnTrades(t *testing.T) {
func TestWsOpenOrders(t *testing.T) {
t.Parallel()
n := new(Kraken)
k := new(Kraken) //nolint:govet // Intentional shadow to avoid future copy/paste mistakes
require.NoError(t, testexch.Setup(k), "Test instance Setup must not error")
testexch.UpdatePairsOnce(t, k)
sharedtestvalues.TestFixtureToDataHandler(t, k, n, "testdata/wsOpenTrades.json", n.wsHandleData)
seen := 0
for reading := true; reading; {
select {
default:
reading = false
case resp := <-n.Websocket.DataHandler:
seen++
switch v := resp.(type) {
case *order.Detail:
switch seen {
case 1:
assert.Equal(t, "OGTT3Y-C6I3P-XRI6HR", v.OrderID, "OrderID")
assert.Equal(t, order.Limit, v.Type, "order type")
assert.Equal(t, order.Sell, v.Side, "order side")
assert.Equal(t, order.Open, v.Status, "order status")
assert.Equal(t, 34.5, v.Price, "price")
assert.Equal(t, 10.00345345, v.Amount, "amount")
case 2:
assert.Equal(t, "OKB55A-UEMMN-YUXM2A", v.OrderID, "OrderID")
assert.Equal(t, order.Market, v.Type, "order type")
assert.Equal(t, order.Buy, v.Side, "order side")
assert.Equal(t, order.Pending, v.Status, "order status")
assert.Equal(t, 0.0, v.Price, "price")
assert.Equal(t, 0.0001, v.Amount, "amount")
assert.Equal(t, time.UnixMicro(1692851641361371), v.Date, "Date")
case 3:
assert.Equal(t, "OKB55A-UEMMN-YUXM2A", v.OrderID, "OrderID")
assert.Equal(t, order.Open, v.Status, "order status")
case 4:
assert.Equal(t, "OKB55A-UEMMN-YUXM2A", v.OrderID, "OrderID")
assert.Equal(t, order.UnknownStatus, v.Status, "order status")
assert.Equal(t, 26425.2, v.AverageExecutedPrice, "AverageExecutedPrice")
assert.Equal(t, 0.0001, v.ExecutedAmount, "ExecutedAmount")
assert.Equal(t, 0.0, v.RemainingAmount, "RemainingAmount") // Not in the message; Testing regression to bad derivation
assert.Equal(t, 0.00687, v.Fee, "Fee")
case 5:
assert.Equal(t, "OKB55A-UEMMN-YUXM2A", v.OrderID, "OrderID")
assert.Equal(t, order.Closed, v.Status, "order status")
assert.Equal(t, 0.0001, v.ExecutedAmount, "ExecutedAmount")
assert.Equal(t, 26425.2, v.AverageExecutedPrice, "AverageExecutedPrice")
assert.Equal(t, 0.00687, v.Fee, "Fee")
assert.Equal(t, time.UnixMicro(1692851641361447), v.LastUpdated, "LastUpdated")
case 6:
assert.Equal(t, "OGTT3Y-C6I3P-XRI6HR", v.OrderID, "OrderID")
assert.Equal(t, order.UnknownStatus, v.Status, "order status")
assert.Equal(t, 10.00345345, v.ExecutedAmount, "ExecutedAmount")
assert.Equal(t, 0.001, v.Fee, "Fee")
assert.Equal(t, 34.5, v.AverageExecutedPrice, "AverageExecutedPrice")
case 7:
assert.Equal(t, "OGTT3Y-C6I3P-XRI6HR", v.OrderID, "OrderID")
assert.Equal(t, order.Closed, v.Status, "order status")
assert.Equal(t, time.UnixMicro(1692675961789052), v.LastUpdated, "LastUpdated")
assert.Equal(t, 10.00345345, v.ExecutedAmount, "ExecutedAmount")
assert.Equal(t, 0.001, v.Fee, "Fee")
assert.Equal(t, 34.5, v.AverageExecutedPrice, "AverageExecutedPrice")
reading = false
}
default:
t.Errorf("Unexpected type in DataHandler: %T (%s)", v, v)
testexch.FixtureToDataHandler(t, "testdata/wsOpenTrades.json", k.wsHandleData)
close(k.Websocket.DataHandler)
assert.Len(t, k.Websocket.DataHandler, 7, "Should see 7 orders")
for resp := range k.Websocket.DataHandler {
switch v := resp.(type) {
case *order.Detail:
switch len(k.Websocket.DataHandler) {
case 6:
assert.Equal(t, "OGTT3Y-C6I3P-XRI6HR", v.OrderID, "OrderID")
assert.Equal(t, order.Limit, v.Type, "order type")
assert.Equal(t, order.Sell, v.Side, "order side")
assert.Equal(t, order.Open, v.Status, "order status")
assert.Equal(t, 34.5, v.Price, "price")
assert.Equal(t, 10.00345345, v.Amount, "amount")
case 5:
assert.Equal(t, "OKB55A-UEMMN-YUXM2A", v.OrderID, "OrderID")
assert.Equal(t, order.Market, v.Type, "order type")
assert.Equal(t, order.Buy, v.Side, "order side")
assert.Equal(t, order.Pending, v.Status, "order status")
assert.Equal(t, 0.0, v.Price, "price")
assert.Equal(t, 0.0001, v.Amount, "amount")
assert.Equal(t, time.UnixMicro(1692851641361371), v.Date, "Date")
case 4:
assert.Equal(t, "OKB55A-UEMMN-YUXM2A", v.OrderID, "OrderID")
assert.Equal(t, order.Open, v.Status, "order status")
case 3:
assert.Equal(t, "OKB55A-UEMMN-YUXM2A", v.OrderID, "OrderID")
assert.Equal(t, order.UnknownStatus, v.Status, "order status")
assert.Equal(t, 26425.2, v.AverageExecutedPrice, "AverageExecutedPrice")
assert.Equal(t, 0.0001, v.ExecutedAmount, "ExecutedAmount")
assert.Equal(t, 0.0, v.RemainingAmount, "RemainingAmount") // Not in the message; Testing regression to bad derivation
assert.Equal(t, 0.00687, v.Fee, "Fee")
case 2:
assert.Equal(t, "OKB55A-UEMMN-YUXM2A", v.OrderID, "OrderID")
assert.Equal(t, order.Closed, v.Status, "order status")
assert.Equal(t, 0.0001, v.ExecutedAmount, "ExecutedAmount")
assert.Equal(t, 26425.2, v.AverageExecutedPrice, "AverageExecutedPrice")
assert.Equal(t, 0.00687, v.Fee, "Fee")
assert.Equal(t, time.UnixMicro(1692851641361447), v.LastUpdated, "LastUpdated")
case 1:
assert.Equal(t, "OGTT3Y-C6I3P-XRI6HR", v.OrderID, "OrderID")
assert.Equal(t, order.UnknownStatus, v.Status, "order status")
assert.Equal(t, 10.00345345, v.ExecutedAmount, "ExecutedAmount")
assert.Equal(t, 0.001, v.Fee, "Fee")
assert.Equal(t, 34.5, v.AverageExecutedPrice, "AverageExecutedPrice")
case 0:
assert.Equal(t, "OGTT3Y-C6I3P-XRI6HR", v.OrderID, "OrderID")
assert.Equal(t, order.Closed, v.Status, "order status")
assert.Equal(t, time.UnixMicro(1692675961789052), v.LastUpdated, "LastUpdated")
assert.Equal(t, 10.00345345, v.ExecutedAmount, "ExecutedAmount")
assert.Equal(t, 0.001, v.Fee, "Fee")
assert.Equal(t, 34.5, v.AverageExecutedPrice, "AverageExecutedPrice")
}
case error:
t.Error(v)
default:
t.Errorf("Unexpected type in DataHandler: %T (%s)", v, v)
}
}
assert.Equal(t, 7, seen, "number of DataHandler emissions")
}
func TestWsAddOrderJSON(t *testing.T) {

View File

@@ -1988,8 +1988,9 @@ func TestGetAuthenticatedServersInstances(t *testing.T) {
}
func TestPushData(t *testing.T) {
n := new(Kucoin)
sharedtestvalues.TestFixtureToDataHandler(t, ku, n, "testdata/wsHandleData.json", ku.wsHandleData)
t.Parallel()
ku := testInstance(t) //nolint:govet // Intentional shadow to avoid future copy/paste mistakes
testexch.FixtureToDataHandler(t, "testdata/wsHandleData.json", ku.wsHandleData)
}
func verifySubs(tb testing.TB, subs []subscription.Subscription, a asset.Item, prefix string, expected ...string) {
@@ -2049,14 +2050,10 @@ func TestGenerateDefaultSubscriptions(t *testing.T) {
func TestGenerateAuthSubscriptions(t *testing.T) {
t.Parallel()
// Create a parallel safe Kucoin to mess with
nu := new(Kucoin)
nu.Base.Features = ku.Base.Features
assert.NoError(t, nu.CurrencyPairs.Load(&ku.CurrencyPairs), "Loading Pairs should not error")
nu.Websocket = sharedtestvalues.NewTestWebsocket()
nu.Websocket.SetCanUseAuthenticatedEndpoints(true)
ku := testInstance(t) //nolint:govet // Intentional shadow to avoid future copy/paste mistakes
ku.Websocket.SetCanUseAuthenticatedEndpoints(true)
subs, err := nu.GenerateDefaultSubscriptions()
subs, err := ku.GenerateDefaultSubscriptions()
assert.NoError(t, err, "GenerateDefaultSubscriptions with Auth should not error")
assert.Len(t, subs, 24, "Should generate the correct number of subs when logged in")
@@ -2086,17 +2083,12 @@ func TestGenerateAuthSubscriptions(t *testing.T) {
func TestGenerateCandleSubscription(t *testing.T) {
t.Parallel()
// Create a parallel safe Kucoin to mess with
nu := new(Kucoin)
nu.Base.Features = ku.Base.Features
nu.Websocket = sharedtestvalues.NewTestWebsocket()
assert.NoError(t, nu.CurrencyPairs.Load(&ku.CurrencyPairs), "Loading Pairs should not error")
nu.Features.Subscriptions = []*subscription.Subscription{
ku := testInstance(t) //nolint:govet // Intentional shadow to avoid future copy/paste mistakes
ku.Features.Subscriptions = []*subscription.Subscription{
{Channel: subscription.CandlesChannel, Interval: kline.FourHour},
}
subs, err := nu.GenerateDefaultSubscriptions()
subs, err := ku.GenerateDefaultSubscriptions()
assert.NoError(t, err, "GenerateDefaultSubscriptions with Candles should not error")
assert.Len(t, subs, 6, "Should generate the correct number of subs for candles")
@@ -2111,17 +2103,12 @@ func TestGenerateCandleSubscription(t *testing.T) {
func TestGenerateMarketSubscription(t *testing.T) {
t.Parallel()
// Create a parallel safe Kucoin to mess with
nu := new(Kucoin)
nu.Base.Features = ku.Base.Features
nu.Websocket = sharedtestvalues.NewTestWebsocket()
assert.NoError(t, nu.CurrencyPairs.Load(&ku.CurrencyPairs), "Loading Pairs should not error")
nu.Features.Subscriptions = []*subscription.Subscription{
ku := testInstance(t) //nolint:govet // Intentional shadow to avoid future copy/paste mistakes
ku.Features.Subscriptions = []*subscription.Subscription{
{Channel: marketSnapshotChannel},
}
subs, err := nu.GenerateDefaultSubscriptions()
subs, err := ku.GenerateDefaultSubscriptions()
assert.NoError(t, err, "GenerateDefaultSubscriptions with MarketSnapshot should not error")
assert.Len(t, subs, 7, "Should generate the correct number of subs for snapshot")
@@ -2490,59 +2477,51 @@ func TestProcessOrderbook(t *testing.T) {
func TestProcessMarketSnapshot(t *testing.T) {
t.Parallel()
n := new(Kucoin)
sharedtestvalues.TestFixtureToDataHandler(t, ku, n, "testdata/wsMarketSnapshot.json", n.wsHandleData)
seen := 0
ku := testInstance(t) //nolint:govet // Intentional shadow to avoid future copy/paste mistakes
testexch.FixtureToDataHandler(t, "testdata/wsMarketSnapshot.json", ku.wsHandleData)
close(ku.Websocket.DataHandler)
assert.Len(t, ku.Websocket.DataHandler, 4, "Should see 4 tickers")
seenAssetTypes := map[asset.Item]int{}
for reading := true; reading; {
select {
default:
reading = false
case resp := <-n.GetBase().Websocket.DataHandler:
seen++
switch v := resp.(type) {
case *ticker.Price:
switch seen {
case 1:
assert.Equal(t, asset.Margin, v.AssetType, "AssetType")
assert.Equal(t, time.UnixMilli(1700555342007), v.LastUpdated, "datetime")
assert.Equal(t, 0.004445, v.High, "high")
assert.Equal(t, 0.004415, v.Last, "lastTradedPrice")
assert.Equal(t, 0.004191, v.Low, "low")
assert.Equal(t, currency.NewPairWithDelimiter("TRX", "BTC", "-"), v.Pair, "symbol")
assert.Equal(t, 13097.3357, v.Volume, "volume")
assert.Equal(t, 57.44552981, v.QuoteVolume, "volValue")
case 2, 3:
assert.Equal(t, time.UnixMilli(1700555340197), v.LastUpdated, "datetime")
assert.Contains(t, []asset.Item{asset.Spot, asset.Margin}, v.AssetType, "AssetType is Spot or Margin")
seenAssetTypes[v.AssetType]++
assert.Equal(t, 1, seenAssetTypes[v.AssetType], "Each Asset Type is sent only once per unique snapshot")
assert.Equal(t, 0.054846, v.High, "high")
assert.Equal(t, 0.053778, v.Last, "lastTradedPrice")
assert.Equal(t, 0.05364, v.Low, "low")
assert.Equal(t, currency.NewPairWithDelimiter("ETH", "BTC", "-"), v.Pair, "symbol")
assert.Equal(t, 2958.3139116, v.Volume, "volume")
assert.Equal(t, 160.7847672784213, v.QuoteVolume, "volValue")
case 4:
assert.Equal(t, asset.Spot, v.AssetType, "AssetType")
assert.Equal(t, time.UnixMilli(1700555342151), v.LastUpdated, "datetime")
assert.Equal(t, 37750.0, v.High, "high")
assert.Equal(t, 37366.8, v.Last, "lastTradedPrice")
assert.Equal(t, 36700.0, v.Low, "low")
assert.Equal(t, currency.NewPairWithDelimiter("BTC", "USDT", "-"), v.Pair, "symbol")
assert.Equal(t, 2900.37846402, v.Volume, "volume")
assert.Equal(t, 108210331.34015164, v.QuoteVolume, "volValue")
default:
t.Errorf("Got an unexpected *ticker.Price: %v", v)
}
case error:
t.Error(v)
default:
t.Errorf("Got unexpected data: %T %v", v, v)
for resp := range ku.Websocket.DataHandler {
switch v := resp.(type) {
case *ticker.Price:
switch len(ku.Websocket.DataHandler) {
case 3:
assert.Equal(t, asset.Margin, v.AssetType, "AssetType")
assert.Equal(t, time.UnixMilli(1700555342007), v.LastUpdated, "datetime")
assert.Equal(t, 0.004445, v.High, "high")
assert.Equal(t, 0.004415, v.Last, "lastTradedPrice")
assert.Equal(t, 0.004191, v.Low, "low")
assert.Equal(t, currency.NewPairWithDelimiter("TRX", "BTC", "-"), v.Pair, "symbol")
assert.Equal(t, 13097.3357, v.Volume, "volume")
assert.Equal(t, 57.44552981, v.QuoteVolume, "volValue")
case 2, 1:
assert.Equal(t, time.UnixMilli(1700555340197), v.LastUpdated, "datetime")
assert.Contains(t, []asset.Item{asset.Spot, asset.Margin}, v.AssetType, "AssetType is Spot or Margin")
seenAssetTypes[v.AssetType]++
assert.Equal(t, 1, seenAssetTypes[v.AssetType], "Each Asset Type is sent only once per unique snapshot")
assert.Equal(t, 0.054846, v.High, "high")
assert.Equal(t, 0.053778, v.Last, "lastTradedPrice")
assert.Equal(t, 0.05364, v.Low, "low")
assert.Equal(t, currency.NewPairWithDelimiter("ETH", "BTC", "-"), v.Pair, "symbol")
assert.Equal(t, 2958.3139116, v.Volume, "volume")
assert.Equal(t, 160.7847672784213, v.QuoteVolume, "volValue")
case 0:
assert.Equal(t, asset.Spot, v.AssetType, "AssetType")
assert.Equal(t, time.UnixMilli(1700555342151), v.LastUpdated, "datetime")
assert.Equal(t, 37750.0, v.High, "high")
assert.Equal(t, 37366.8, v.Last, "lastTradedPrice")
assert.Equal(t, 36700.0, v.Low, "low")
assert.Equal(t, currency.NewPairWithDelimiter("BTC", "USDT", "-"), v.Pair, "symbol")
assert.Equal(t, 2900.37846402, v.Volume, "volume")
assert.Equal(t, 108210331.34015164, v.QuoteVolume, "volValue")
}
case error:
t.Error(v)
default:
t.Errorf("Got unexpected data: %T %v", v, v)
}
}
assert.Equal(t, 4, seen, "Number of messages")
}
func TestSubscribeMarketSnapshot(t *testing.T) {
@@ -2735,16 +2714,16 @@ func TestUpdateOrderExecutionLimits(t *testing.T) {
func TestGetOpenInterest(t *testing.T) {
t.Parallel()
nu := new(Kucoin)
require.NoError(t, testexch.Setup(nu), "Test exchange Setup must not error")
_, err := nu.GetOpenInterest(context.Background(), key.PairAsset{
ku := testInstance(t) //nolint:govet // Intentional shadow to avoid future copy/paste mistakes
_, err := ku.GetOpenInterest(context.Background(), key.PairAsset{
Base: currency.ETH.Item,
Quote: currency.USDT.Item,
Asset: asset.USDTMarginedFutures,
})
assert.ErrorIs(t, err, asset.ErrNotSupported)
resp, err := nu.GetOpenInterest(context.Background(), key.PairAsset{
resp, err := ku.GetOpenInterest(context.Background(), key.PairAsset{
Base: futuresTradablePair.Base.Item,
Quote: futuresTradablePair.Quote.Item,
Asset: asset.Futures,
@@ -2753,8 +2732,8 @@ func TestGetOpenInterest(t *testing.T) {
assert.NotEmpty(t, resp)
cp1 := currency.NewPair(currency.ETH, currency.USDTM)
sharedtestvalues.SetupCurrencyPairsForExchangeAsset(t, nu, asset.Futures, cp1)
resp, err = nu.GetOpenInterest(context.Background(),
sharedtestvalues.SetupCurrencyPairsForExchangeAsset(t, ku, asset.Futures, cp1)
resp, err = ku.GetOpenInterest(context.Background(),
key.PairAsset{
Base: futuresTradablePair.Base.Item,
Quote: futuresTradablePair.Quote.Item,
@@ -2769,7 +2748,7 @@ func TestGetOpenInterest(t *testing.T) {
assert.NoError(t, err)
assert.NotEmpty(t, resp)
resp, err = nu.GetOpenInterest(context.Background())
resp, err = ku.GetOpenInterest(context.Background())
assert.NoError(t, err)
assert.NotEmpty(t, resp)
}
@@ -2786,3 +2765,15 @@ func TestGetCurrencyTradeURL(t *testing.T) {
assert.NotEmpty(t, resp)
}
}
// testInstance returns a local Kucoin for isolated testing
func testInstance(tb testing.TB) *Kucoin {
tb.Helper()
ku := new(Kucoin)
require.NoError(tb, testexch.Setup(ku), "Test instance Setup must not error")
ku.obm = &orderbookManager{
state: make(map[currency.Code]map[currency.Code]map[asset.Item]*update),
jobs: make(chan job, maxWSOrderbookJobs),
}
return ku
}

View File

@@ -3065,15 +3065,6 @@ func (ok *Okx) GetUnderlying(pair currency.Pair, a asset.Item) (string, error) {
return pair.Base.String() + format.Delimiter + pair.Quote.String(), nil
}
// GetPairFromInstrumentID returns a currency pair give an instrument ID and asset Item, which represents the instrument type.
func (ok *Okx) GetPairFromInstrumentID(instrumentID string) (currency.Pair, error) {
codes := strings.Split(instrumentID, ok.CurrencyPairs.RequestFormat.Delimiter)
if len(codes) >= 2 {
instrumentID = codes[0] + ok.CurrencyPairs.RequestFormat.Delimiter + strings.Join(codes[1:], ok.CurrencyPairs.RequestFormat.Delimiter)
}
return currency.NewPairFromString(instrumentID)
}
// GetOrderBookDepth returns the recent order asks and bids before specified timestamp.
func (ok *Okx) GetOrderBookDepth(ctx context.Context, instrumentID string, depth int64) (*OrderBookResponse, error) {
params := url.Values{}
@@ -4320,11 +4311,15 @@ func (ok *Okx) GetAssetsFromInstrumentTypeOrID(instType, instrumentID string) ([
if instrumentID == "" {
return nil, fmt.Errorf("%w instrumentID", errEmptyArgument)
}
splitSymbol := strings.Split(instrumentID, ok.CurrencyPairs.RequestFormat.Delimiter)
pf, err := ok.CurrencyPairs.GetFormat(asset.Spot, true)
if err != nil {
return nil, err
}
splitSymbol := strings.Split(instrumentID, pf.Delimiter)
if len(splitSymbol) <= 1 {
return nil, fmt.Errorf("%w %v", currency.ErrCurrencyNotSupported, instrumentID)
}
pair, err := currency.NewPairDelimiter(instrumentID, ok.CurrencyPairs.RequestFormat.Delimiter)
pair, err := currency.NewPairDelimiter(instrumentID, pf.Delimiter)
if err != nil {
return nil, err
}

View File

@@ -2206,24 +2206,6 @@ func TestWithdraw(t *testing.T) {
}
}
func TestGetPairFromInstrumentID(t *testing.T) {
t.Parallel()
instruments := []string{
"BTC-USDT",
"BTC-USDT-SWAP",
"BTC-USDT-ER33234",
}
if _, err := ok.GetPairFromInstrumentID(instruments[0]); err != nil {
t.Error("Okx GetPairFromInstrumentID() error", err)
}
if _, ere := ok.GetPairFromInstrumentID(instruments[1]); ere != nil {
t.Error("Okx GetPairFromInstrumentID() error", ere)
}
if _, erf := ok.GetPairFromInstrumentID(instruments[2]); erf != nil {
t.Error("Okx GetPairFromInstrumentID() error", erf)
}
}
func TestGetActiveOrders(t *testing.T) {
t.Parallel()
sharedtestvalues.SkipTestIfCredentialsUnset(t, ok)
@@ -2534,68 +2516,63 @@ func TestBalanceAndPosition(t *testing.T) {
func TestOrderPushData(t *testing.T) {
t.Parallel()
n := new(Okx)
sharedtestvalues.TestFixtureToDataHandler(t, ok, n, "testdata/wsOrders.json", n.WsHandleData)
seen := 0
for reading := true; reading; {
select {
default:
reading = false
case resp := <-n.GetBase().Websocket.DataHandler:
seen++
switch v := resp.(type) {
case *order.Detail:
switch seen {
case 1:
assert.Equal(t, "452197707845865472", v.OrderID, "OrderID")
assert.Equal(t, "HamsterParty14", v.ClientOrderID, "ClientOrderID")
assert.Equal(t, asset.Spot, v.AssetType, "AssetType")
assert.Equal(t, order.Sell, v.Side, "Side")
assert.Equal(t, order.Filled, v.Status, "Status")
assert.Equal(t, order.Limit, v.Type, "Type")
assert.Equal(t, currency.NewPairWithDelimiter("BTC", "USDT", "-"), v.Pair, "Pair")
assert.Equal(t, 31527.1, v.AverageExecutedPrice, "AverageExecutedPrice")
assert.Equal(t, time.UnixMilli(1654084334977), v.Date, "Date")
assert.Equal(t, time.UnixMilli(1654084353263), v.CloseTime, "CloseTime")
assert.Equal(t, 0.001, v.Amount, "Amount")
assert.Equal(t, 0.001, v.ExecutedAmount, "ExecutedAmount")
assert.Equal(t, 0.000, v.RemainingAmount, "RemainingAmount")
assert.Equal(t, 31527.1, v.Price, "Price")
assert.Equal(t, 0.02522168, v.Fee, "Fee")
assert.Equal(t, currency.USDT, v.FeeAsset, "FeeAsset")
case 2:
assert.Equal(t, "620258920632008725", v.OrderID, "OrderID")
assert.Equal(t, asset.Spot, v.AssetType, "AssetType")
assert.Equal(t, order.Market, v.Type, "Type")
assert.Equal(t, order.Sell, v.Side, "Side")
assert.Equal(t, order.Active, v.Status, "Status")
assert.Equal(t, 0.0, v.Amount, "Amount should be 0 for a market sell")
assert.Equal(t, 10.0, v.QuoteAmount, "QuoteAmount")
case 3:
assert.Equal(t, "620258920632008725", v.OrderID, "OrderID")
assert.Equal(t, 10.0, v.QuoteAmount, "QuoteAmount")
assert.Equal(t, 0.00038127046945832905, v.Amount, "Amount")
assert.Equal(t, 0.010000249968, v.Fee, "Fee")
assert.Equal(t, 0.0, v.RemainingAmount, "RemainingAmount")
assert.Equal(t, 0.00038128, v.ExecutedAmount, "ExecutedAmount")
assert.Equal(t, order.PartiallyFilled, v.Status, "Status")
case 4:
assert.Equal(t, "620258920632008725", v.OrderID, "OrderID")
assert.Equal(t, 10.0, v.QuoteAmount, "QuoteAmount")
assert.Equal(t, 0.010000249968, v.Fee, "Fee")
assert.Equal(t, 0.0, v.RemainingAmount, "RemainingAmount")
assert.Equal(t, 0.00038128, v.ExecutedAmount, "ExecutedAmount")
assert.Equal(t, 0.00038128, v.Amount, "Amount should be derived because order filled")
assert.Equal(t, order.Filled, v.Status, "Status")
}
case error:
t.Error(v)
default:
t.Errorf("Got unexpected data: %T %v", v, v)
ok := new(Okx) //nolint:govet // Intentional shadow to avoid future copy/paste mistakes
require.NoError(t, testexch.Setup(ok), "Test instance Setup must not error")
testexch.FixtureToDataHandler(t, "testdata/wsOrders.json", ok.WsHandleData)
close(ok.Websocket.DataHandler)
assert.Len(t, ok.Websocket.DataHandler, 4, "Should see 4 orders")
for resp := range ok.Websocket.DataHandler {
switch v := resp.(type) {
case *order.Detail:
switch len(ok.Websocket.DataHandler) {
case 3:
assert.Equal(t, "452197707845865472", v.OrderID, "OrderID")
assert.Equal(t, "HamsterParty14", v.ClientOrderID, "ClientOrderID")
assert.Equal(t, asset.Spot, v.AssetType, "AssetType")
assert.Equal(t, order.Sell, v.Side, "Side")
assert.Equal(t, order.Filled, v.Status, "Status")
assert.Equal(t, order.Limit, v.Type, "Type")
assert.Equal(t, currency.NewPairWithDelimiter("BTC", "USDT", "-"), v.Pair, "Pair")
assert.Equal(t, 31527.1, v.AverageExecutedPrice, "AverageExecutedPrice")
assert.Equal(t, time.UnixMilli(1654084334977), v.Date, "Date")
assert.Equal(t, time.UnixMilli(1654084353263), v.CloseTime, "CloseTime")
assert.Equal(t, 0.001, v.Amount, "Amount")
assert.Equal(t, 0.001, v.ExecutedAmount, "ExecutedAmount")
assert.Equal(t, 0.000, v.RemainingAmount, "RemainingAmount")
assert.Equal(t, 31527.1, v.Price, "Price")
assert.Equal(t, 0.02522168, v.Fee, "Fee")
assert.Equal(t, currency.USDT, v.FeeAsset, "FeeAsset")
case 2:
assert.Equal(t, "620258920632008725", v.OrderID, "OrderID")
assert.Equal(t, asset.Spot, v.AssetType, "AssetType")
assert.Equal(t, order.Market, v.Type, "Type")
assert.Equal(t, order.Sell, v.Side, "Side")
assert.Equal(t, order.Active, v.Status, "Status")
assert.Equal(t, 0.0, v.Amount, "Amount should be 0 for a market sell")
assert.Equal(t, 10.0, v.QuoteAmount, "QuoteAmount")
case 1:
assert.Equal(t, "620258920632008725", v.OrderID, "OrderID")
assert.Equal(t, 10.0, v.QuoteAmount, "QuoteAmount")
assert.Equal(t, 0.00038127046945832905, v.Amount, "Amount")
assert.Equal(t, 0.010000249968, v.Fee, "Fee")
assert.Equal(t, 0.0, v.RemainingAmount, "RemainingAmount")
assert.Equal(t, 0.00038128, v.ExecutedAmount, "ExecutedAmount")
assert.Equal(t, order.PartiallyFilled, v.Status, "Status")
case 0:
assert.Equal(t, "620258920632008725", v.OrderID, "OrderID")
assert.Equal(t, 10.0, v.QuoteAmount, "QuoteAmount")
assert.Equal(t, 0.010000249968, v.Fee, "Fee")
assert.Equal(t, 0.0, v.RemainingAmount, "RemainingAmount")
assert.Equal(t, 0.00038128, v.ExecutedAmount, "ExecutedAmount")
assert.Equal(t, 0.00038128, v.Amount, "Amount should be derived because order filled")
assert.Equal(t, order.Filled, v.Status, "Status")
}
case error:
t.Error(v)
default:
t.Errorf("Got unexpected data: %T %v", v, v)
}
}
assert.Equal(t, 4, seen, "Saw 4 records")
}
const algoOrdersPushDataJSON = `{"arg": {"channel": "orders-algo","uid": "77982378738415879","instType": "FUTURES","instId": "BTC-USD-200329"},"data": [{"instType": "FUTURES","instId": "BTC-USD-200329","ordId": "312269865356374016","ccy": "BTC","algoId": "1234","px": "999","sz": "3","tdMode": "cross","tgtCcy": "","notionalUsd": "","ordType": "trigger","side": "buy","posSide": "long","state": "live","lever": "20","tpTriggerPx": "","tpTriggerPxType": "","tpOrdPx": "","slTriggerPx": "","slTriggerPxType": "","triggerPx": "99","triggerPxType": "last","ordPx": "12","actualSz": "","actualPx": "","tag": "adadadadad","actualSide": "","triggerTime": "1597026383085","cTime": "1597026383000"}]}`

View File

@@ -688,7 +688,8 @@ func (ok *Okx) wsProcessIndexCandles(respRaw []byte) error {
if len(response.Data) == 0 {
return errNoCandlestickDataFound
}
pair, err := ok.GetPairFromInstrumentID(response.Argument.InstrumentID)
pair, err := currency.NewPairFromString(response.Argument.InstrumentID)
if err != nil {
return err
}
@@ -750,7 +751,7 @@ func (ok *Okx) wsProcessOrderbook5(data []byte) error {
return err
}
pair, err := ok.GetPairFromInstrumentID(resp.Argument.InstrumentID)
pair, err := currency.NewPairFromString(resp.Argument.InstrumentID)
if err != nil {
return err
}
@@ -809,13 +810,11 @@ func (ok *Okx) wsProcessOrderBooks(data []byte) error {
response.Action != wsOrderbookSnapshot {
return errors.New("invalid order book action")
}
var pair currency.Pair
var assets []asset.Item
assets, err = ok.GetAssetsFromInstrumentTypeOrID(response.Argument.InstrumentType, response.Argument.InstrumentID)
assets, err := ok.GetAssetsFromInstrumentTypeOrID(response.Argument.InstrumentType, response.Argument.InstrumentID)
if err != nil {
return err
}
pair, err = ok.GetPairFromInstrumentID(response.Argument.InstrumentID)
pair, err := currency.NewPairFromString(response.Argument.InstrumentID)
if err != nil {
return err
}
@@ -1062,8 +1061,7 @@ func (ok *Okx) wsProcessTrades(data []byte) error {
}
trades := make([]trade.Data, 0, len(response.Data)*len(assets))
for i := range response.Data {
var pair currency.Pair
pair, err = ok.GetPairFromInstrumentID(response.Data[i].InstrumentID)
pair, err := currency.NewPairFromString(response.Data[i].InstrumentID)
if err != nil {
return err
}
@@ -1086,7 +1084,6 @@ func (ok *Okx) wsProcessTrades(data []byte) error {
// wsProcessOrders handles websocket order push data responses.
func (ok *Okx) wsProcessOrders(respRaw []byte) error {
var response WsOrderResponse
var pair currency.Pair
err := json.Unmarshal(respRaw, &response)
if err != nil {
return err
@@ -1109,7 +1106,7 @@ func (ok *Okx) wsProcessOrders(respRaw []byte) error {
Err: err,
}
}
pair, err = ok.GetPairFromInstrumentID(response.Data[x].InstrumentID)
pair, err := currency.NewPairFromString(response.Data[x].InstrumentID)
if err != nil {
return err
}
@@ -1189,7 +1186,7 @@ func (ok *Okx) wsProcessCandles(respRaw []byte) error {
if len(response.Data) == 0 {
return errNoCandlestickDataFound
}
pair, err := ok.GetPairFromInstrumentID(response.Argument.InstrumentID)
pair, err := currency.NewPairFromString(response.Argument.InstrumentID)
if err != nil {
return err
}
@@ -1260,8 +1257,7 @@ func (ok *Okx) wsProcessTickers(data []byte) error {
if err != nil {
return err
}
var c currency.Pair
c, err = ok.GetPairFromInstrumentID(response.Data[i].InstrumentID)
c, err := currency.NewPairFromString(response.Data[i].InstrumentID)
if err != nil {
return err
}

View File

@@ -259,9 +259,13 @@ func (ok *Okx) FetchTradablePairs(ctx context.Context, a asset.Item) (currency.P
if err != nil {
return nil, err
}
pf, err := ok.CurrencyPairs.GetFormat(a, false)
if err != nil {
return nil, err
}
pairs := make([]currency.Pair, len(insts))
for x := range insts {
pairs[x], err = currency.NewPairDelimiter(insts[x].InstrumentID, ok.CurrencyPairs.ConfigFormat.Delimiter)
pairs[x], err = currency.NewPairDelimiter(insts[x].InstrumentID, pf.Delimiter)
if err != nil {
return nil, err
}
@@ -378,7 +382,7 @@ func (ok *Okx) UpdateTickers(ctx context.Context, assetType asset.Item) error {
}
for y := range ticks {
pair, err := ok.GetPairFromInstrumentID(ticks[y].InstrumentID)
pair, err := currency.NewPairFromString(ticks[y].InstrumentID)
if err != nil {
return err
}
@@ -1192,8 +1196,7 @@ allOrders:
break allOrders
}
orderSide := orderList[i].Side
var pair currency.Pair
pair, err = ok.GetPairFromInstrumentID(orderList[i].InstrumentID)
pair, err := currency.NewPairFromString(orderList[i].InstrumentID)
if err != nil {
return nil, err
}
@@ -1286,8 +1289,7 @@ allOrders:
// reached end of orders to crawl
break allOrders
}
var pair currency.Pair
pair, err = ok.GetPairFromInstrumentID(orderList[i].InstrumentID)
pair, err := currency.NewPairFromString(orderList[i].InstrumentID)
if err != nil {
return nil, err
}

View File

@@ -1,7 +1,6 @@
package sharedtestvalues
import (
"bufio"
"bytes"
"errors"
"fmt"
@@ -9,15 +8,14 @@ import (
"path/filepath"
"regexp"
"strings"
"sync"
"testing"
"time"
"github.com/stretchr/testify/assert"
"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/stream"
"github.com/thrasher-corp/gocryptotrader/exchanges/stream/buffer"
"github.com/thrasher-corp/gocryptotrader/exchanges/subscription"
)
@@ -64,6 +62,7 @@ func NewTestWebsocket() *stream.Websocket {
Subscribe: make(chan []subscription.Subscription, 10),
Unsubscribe: make(chan []subscription.Subscription, 10),
Match: stream.NewMatch(),
Orderbook: buffer.Orderbook{},
}
}
@@ -154,38 +153,6 @@ func ForceFileStandard(t *testing.T, pattern string) error {
return nil
}
// TestFixtureToDataHandler takes a new empty exchange and configures a new websocket handler for it, and squirts the json path contents to it
// It accepts a reader function, which is probably e.wsHandleData but could be anything
func TestFixtureToDataHandler(t *testing.T, seed, e exchange.IBotExchange, fixturePath string, reader func([]byte) error) {
t.Helper()
b := e.GetBase()
seedBase := seed.GetBase()
err := b.CurrencyPairs.Load(&seedBase.CurrencyPairs)
assert.NoError(t, err, "Loading currency pairs should not error")
b.Name = "fixture"
b.Websocket = &stream.Websocket{
Wg: new(sync.WaitGroup),
DataHandler: make(chan interface{}, 128),
}
b.API.Endpoints = b.NewEndpoints()
fixture, err := os.Open(fixturePath)
assert.NoError(t, err, "Opening fixture '%s' should not error", fixturePath)
defer func() {
assert.NoError(t, fixture.Close(), "Closing the fixture file should not error")
}()
s := bufio.NewScanner(fixture)
for s.Scan() {
msg := s.Bytes()
err := reader(msg)
assert.NoErrorf(t, err, "Fixture message should not error:\n%s", msg)
}
assert.NoError(t, s.Err(), "Fixture Scanner should not error")
}
// SetupCurrencyPairsForExchangeAsset enables an asset for an exchange
// and adds the currency pair(s) to the available and enabled list of existing pairs
// if it is already enabled or part of the pairs, no error is raised