Files
gocryptotrader/currency/manager_test.go
Gareth Kirwan 37b1121bbd BTSE: Fix duplicate pair errors on Million pairs (M_*) (#1401)
* BTSE: Fix duplicate error on Million pairs (M_*)

BTSE has listed Pitbull token with two symbols:
PIT-USD and M_PIT-USD for millons of PIT / USD.
The native token is not tradable, so we ignore them and
get a base of M_PIT because that's what later APIs will accept

* BTSE: Fix test errors on locked market

* Common: Improve AppendError and ExcludeError

This change switches from a stateful multiError to caring more about the
Unwrap() []error interface, the same as [go standard
lib](https://github.com/golang/go/blob/go1.21.4/src/errors/wrap.go#L54-L68)

Notably, if we implement Unwrap() []error and do NOT implement Is() then
we get free compatibility with the core functions.

The only distateful thing here is needing to deeply unwrap fmt.Errorf
errors, since they don't flatten. I can't see any way around that

* Pairs: Fix exchange config Pairs loading

When a pair string contained two punctuation runes, the first one is used,
and the configFormat is ignored.

This fix checks the list and corrects any with the wrong delimiter, or
errors if the format is inconsistent.

* BTSE: Fix all tickers retrieved by GetTicker

PR #764 introduced GetTickers, but it wasn't rolled out to BTSE.
This fix ensures that when one ticker is a locked market, the rest continue to
function. Particularly important if the locked market wasn't even
enabled anyway.

* Kucoin: Fix test config future pairs

* BTSE: Remove PIT tests; Token removed

BTSE have removed the PIT token pairs

All these changes stand, and this just removes the test

* ITBit: Fix fatal error on second run

This fix removes incorrect config pair delimiter, because it would be
re-inserted into config the first run, and then error the second time.

This delimiter doesn't match the config we have.
There's no implementation of fetching pairs, so what's in config files
now is all that matters

* Engine: Fix TestConfigAllJsonResponse

* Clarity of non-matching json improved
* Handling for fixing pair delimiters
2023-12-19 14:40:13 +11:00

824 lines
21 KiB
Go

package currency
import (
"encoding/json"
"errors"
"testing"
"github.com/stretchr/testify/assert"
"github.com/thrasher-corp/gocryptotrader/common"
"github.com/thrasher-corp/gocryptotrader/common/convert"
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
)
func initTest(t *testing.T) *PairsManager {
t.Helper()
spotAvailable, err := NewPairsFromStrings([]string{"BTC-USD", "LTC-USD"})
if err != nil {
t.Fatal(err)
}
spotEnabled, err := NewPairsFromStrings([]string{"BTC-USD"})
if err != nil {
t.Fatal(err)
}
spot := &PairStore{
AssetEnabled: convert.BoolPtr(true),
Available: spotAvailable,
Enabled: spotEnabled,
RequestFormat: &PairFormat{Uppercase: true},
ConfigFormat: &PairFormat{Uppercase: true, Delimiter: "-"},
}
futures := &PairStore{
AssetEnabled: convert.BoolPtr(false),
Available: spotAvailable,
Enabled: spotEnabled,
RequestFormat: &PairFormat{Uppercase: true},
ConfigFormat: &PairFormat{Uppercase: true, Delimiter: "-"},
}
var p PairsManager
err = p.Store(asset.Spot, spot)
if err != nil {
t.Fatal(err)
}
err = p.Store(asset.Futures, futures)
if err != nil {
t.Fatal(err)
}
return &p
}
func TestGetAssetTypes(t *testing.T) {
t.Parallel()
p := initTest(t)
a := p.GetAssetTypes(false)
if len(a) != 2 {
t.Errorf("expected 2 but received: %d", len(a))
}
a = p.GetAssetTypes(true)
if len(a) != 1 {
t.Errorf("GetAssetTypes shouldn't be nil")
}
if !a.Contains(asset.Spot) {
t.Errorf("AssetTypeSpot should be in the assets list")
}
}
func TestGet(t *testing.T) {
t.Parallel()
p := initTest(t)
_, err := p.Get(asset.Spot)
if err != nil {
t.Error(err)
}
_, err = p.Get(asset.Empty)
if !errors.Is(err, asset.ErrNotSupported) {
t.Fatalf("received: '%v' but expected: '%v'", err, asset.ErrNotSupported)
}
_, err = p.Get(asset.CoinMarginedFutures)
if !errors.Is(err, asset.ErrNotSupported) {
t.Fatalf("received: '%v' but expected: '%v'", err, asset.ErrNotSupported)
}
}
func TestPairsManagerMatch(t *testing.T) {
t.Parallel()
p := &PairsManager{}
_, err := p.Match("", 1337)
if !errors.Is(err, ErrSymbolStringEmpty) {
t.Fatalf("received: '%v' but expected: '%v'", err, ErrSymbolStringEmpty)
}
_, err = p.Match("sillyBilly", 1337)
if !errors.Is(err, errPairMatcherIsNil) {
t.Fatalf("received: '%v' but expected: '%v'", err, errPairMatcherIsNil)
}
p = initTest(t)
_, err = p.Match("sillyBilly", 1337)
if !errors.Is(err, ErrPairNotFound) {
t.Fatalf("received: '%v' but expected: '%v'", err, ErrPairNotFound)
}
_, err = p.Match("sillyBilly", asset.Spot)
if !errors.Is(err, ErrPairNotFound) {
t.Fatalf("received: '%v' but expected: '%v'", err, ErrPairNotFound)
}
whatIgot, err := p.Match("bTCuSD", asset.Spot)
if !errors.Is(err, nil) {
t.Fatalf("received: '%v' but expected: '%v'", err, nil)
}
whatIwant, err := NewPairFromString("btc-usd")
if err != nil {
t.Fatal(err)
}
if !whatIgot.Equal(whatIwant) {
t.Fatal("expected btc-usd")
}
}
func TestStore(t *testing.T) {
t.Parallel()
availPairs, err := NewPairsFromStrings([]string{"BTC-USD", "LTC-USD"})
if err != nil {
t.Fatal(err)
}
enabledPairs, err := NewPairsFromStrings([]string{"BTC-USD"})
if err != nil {
t.Fatal(err)
}
p := initTest(t)
err = p.Store(asset.Futures,
&PairStore{
Available: availPairs,
Enabled: enabledPairs,
RequestFormat: &PairFormat{
Uppercase: true,
},
ConfigFormat: &PairFormat{
Uppercase: true,
Delimiter: "-",
},
},
)
if err != nil {
t.Fatal(err)
}
f, err := p.Get(asset.Futures)
if err != nil {
t.Fatal(err)
}
if f == nil {
t.Error("Futures assets shouldn't be nil")
}
err = p.Store(asset.Empty, nil)
if !errors.Is(err, asset.ErrNotSupported) {
t.Fatalf("received: '%v' but expected: '%v'", err, asset.ErrNotSupported)
}
err = p.Store(asset.Futures, nil)
if !errors.Is(err, errPairStoreIsNil) {
t.Fatalf("received: '%v' but expected: '%v'", err, errPairStoreIsNil)
}
}
func TestDelete(t *testing.T) {
t.Parallel()
p := initTest(t)
p.Pairs = nil
p.Delete(asset.Spot)
btcusdPairs, err := NewPairsFromStrings([]string{"BTC-USD"})
if err != nil {
t.Fatal(err)
}
err = p.Store(asset.Spot, &PairStore{Available: btcusdPairs})
if err != nil {
t.Fatal(err)
}
p.Delete(asset.UpsideProfitContract)
spotPS, err := p.Get(asset.Spot)
if err != nil {
t.Fatal(err)
}
if spotPS == nil {
t.Error("AssetTypeSpot should exist")
}
p.Delete(asset.Spot)
if _, err := p.Get(asset.Spot); err == nil {
t.Error("Delete should have deleted AssetTypeSpot")
}
}
func TestGetPairs(t *testing.T) {
t.Parallel()
p := initTest(t)
p.Pairs = nil
pairs, err := p.GetPairs(asset.Spot, true)
if err != nil {
t.Fatal(err)
}
if pairs != nil {
t.Fatal("pairs shouldn't be populated")
}
p = initTest(t)
pairs, err = p.GetPairs(asset.Spot, true)
if err != nil {
t.Fatal(err)
}
if pairs == nil {
t.Fatal("pairs should be populated")
}
pairs, err = p.GetPairs(asset.Empty, true)
if !errors.Is(err, asset.ErrNotSupported) {
t.Fatalf("received: '%v' but expected: '%v'", err, asset.ErrNotSupported)
}
if pairs != nil {
t.Fatal("pairs shouldn't be populated")
}
superfluous := NewPair(DASH, USDT)
newPairs := p.Pairs[asset.Spot].Enabled.Add(superfluous)
p.Pairs[asset.Spot].Enabled = newPairs
_, err = p.GetPairs(asset.Spot, true)
if err == nil {
t.Fatal("error cannot be nil")
}
}
func TestStoreFormat(t *testing.T) {
t.Parallel()
p := &PairsManager{}
err := p.StoreFormat(0, &PairFormat{Delimiter: "~"}, true)
if !errors.Is(err, asset.ErrNotSupported) {
t.Fatalf("received: %v but expected: %v", err, asset.ErrNotSupported)
}
err = p.StoreFormat(asset.Spot, nil, true)
if !errors.Is(err, errPairFormatIsNil) {
t.Fatalf("received: %v but expected: %v", err, errPairFormatIsNil)
}
err = p.StoreFormat(asset.Spot, &PairFormat{Delimiter: "~"}, true)
if !errors.Is(err, nil) {
t.Fatalf("received: %v but expected: %v", err, nil)
}
ps, err := p.Get(asset.Spot)
if err != nil {
t.Fatal(err)
}
if ps.ConfigFormat.Delimiter != "~" {
t.Fatal("unexpected value")
}
err = p.StoreFormat(asset.Spot, &PairFormat{Delimiter: "/"}, false)
if !errors.Is(err, nil) {
t.Fatalf("received: %v but expected: %v", err, nil)
}
ps, err = p.Get(asset.Spot)
if err != nil {
t.Fatal(err)
}
if ps.RequestFormat.Delimiter != "/" {
t.Fatal("unexpected value")
}
}
func TestStorePairs(t *testing.T) {
t.Parallel()
p := initTest(t)
err := p.StorePairs(0, nil, false)
if !errors.Is(err, asset.ErrNotSupported) {
t.Fatalf("received: %v but expected: %v", err, asset.ErrNotSupported)
}
p.Pairs = nil
ethusdPairs, err := NewPairsFromStrings([]string{"ETH-USD"})
if err != nil {
t.Fatal(err)
}
err = p.StorePairs(asset.Spot, ethusdPairs, false)
if !errors.Is(err, nil) {
t.Fatalf("received: %v but expected: %v", err, nil)
}
pairs, err := p.GetPairs(asset.Spot, false)
if err != nil {
t.Fatal(err)
}
ethusd, err := NewPairFromString("ETH-USD")
if err != nil {
t.Fatal(err)
}
if !pairs.Contains(ethusd, true) {
t.Errorf("TestStorePairs failed, unexpected result")
}
p = initTest(t)
err = p.StorePairs(asset.Spot, ethusdPairs, false)
if !errors.Is(err, nil) {
t.Fatalf("received: %v but expected: %v", err, nil)
}
pairs, err = p.GetPairs(asset.Spot, false)
if err != nil {
t.Fatal(err)
}
if pairs == nil {
t.Errorf("pairs should be populated")
}
if !pairs.Contains(ethusd, true) {
t.Errorf("TestStorePairs failed, unexpected result")
}
ethkrwPairs, err := NewPairsFromStrings([]string{"ETH-KRW"})
if err != nil {
t.Error(err)
}
err = p.StorePairs(asset.Futures, ethkrwPairs, true)
if !errors.Is(err, nil) {
t.Fatalf("received: %v but expected: %v", err, nil)
}
err = p.StorePairs(asset.Futures, ethkrwPairs, false)
if !errors.Is(err, nil) {
t.Fatalf("received: %v but expected: %v", err, nil)
}
pairs, err = p.GetPairs(asset.Futures, true)
if err != nil {
t.Fatal(err)
}
if pairs == nil {
t.Errorf("pairs futures should be populated")
}
ethkrw, err := NewPairFromString("ETH-KRW")
if err != nil {
t.Error(err)
}
if !pairs.Contains(ethkrw, true) {
t.Errorf("TestStorePairs failed, unexpected result")
}
}
func TestDisablePair(t *testing.T) {
t.Parallel()
p := initTest(t)
if err := p.DisablePair(asset.Empty, EMPTYPAIR); !errors.Is(err, asset.ErrNotSupported) {
t.Fatalf("received: '%v' but expected: '%v'", err, asset.ErrNotSupported)
}
if err := p.DisablePair(asset.Spot, EMPTYPAIR); !errors.Is(err, ErrCurrencyPairEmpty) {
t.Fatalf("received: '%v' but expected: '%v'", err, ErrCurrencyPairEmpty)
}
p.Pairs = nil
// Test disabling a pair when the pair manager is not initialised
if err := p.DisablePair(asset.Spot, NewPair(BTC, USD)); err == nil {
t.Error("unexpected result")
}
// Test asset type which doesn't exist
p = initTest(t)
if err := p.DisablePair(asset.Futures, EMPTYPAIR); err == nil {
t.Error("unexpected result")
}
// Test asset type which has an empty pair store
p.Pairs[asset.Spot] = nil
if err := p.DisablePair(asset.Spot, EMPTYPAIR); err == nil {
t.Error("unexpected result")
}
// Test disabling a pair which isn't enabled
p = initTest(t)
if err := p.DisablePair(asset.Spot, NewPair(LTC, USD)); err == nil {
t.Error("unexpected result")
}
// Test disabling a valid pair and ensure nil is empty
if err := p.DisablePair(asset.Spot, NewPair(BTC, USD)); err != nil {
t.Error("unexpected result")
}
}
func TestEnablePair(t *testing.T) {
t.Parallel()
p := initTest(t)
if err := p.EnablePair(asset.Empty, NewPair(BTC, USD)); !errors.Is(err, asset.ErrNotSupported) {
t.Fatalf("received: '%v' but expected: '%v'", err, asset.ErrNotSupported)
}
p.Pairs = nil
// Test enabling a pair when the pair manager is not initialised
if err := p.EnablePair(asset.Spot, NewPair(BTC, USD)); err == nil {
t.Error("unexpected result")
}
// Test asset type which doesn't exist
p = initTest(t)
if err := p.EnablePair(asset.Futures, EMPTYPAIR); err == nil {
t.Error("unexpected result")
}
// Test asset type which has an empty pair store
p.Pairs[asset.Spot] = nil
if err := p.EnablePair(asset.Spot, EMPTYPAIR); err == nil {
t.Error("unexpected result")
}
// Test enabling a pair which isn't in the list of available pairs
p = initTest(t)
if err := p.EnablePair(asset.Spot, NewPair(ETH, USD)); err == nil {
t.Error("unexpected result")
}
// Test enabling a pair which already is enabled
if err := p.EnablePair(asset.Spot, NewPair(BTC, USD)); err == nil {
t.Error("unexpected result")
}
// Test enabling a valid pair
if err := p.EnablePair(asset.Spot, NewPair(LTC, USD)); err != nil {
t.Error("unexpected result")
}
}
func TestIsAssetEnabled_SetAssetEnabled(t *testing.T) {
t.Parallel()
p := initTest(t)
err := p.IsAssetEnabled(asset.Empty)
if !errors.Is(err, asset.ErrNotSupported) {
t.Fatalf("received: '%v' but expected: '%v'", err, asset.ErrNotSupported)
}
p.Pairs = nil
// Test enabling a pair when the pair manager is not initialised
err = p.IsAssetEnabled(asset.Spot)
if err == nil {
t.Error("unexpected result")
}
err = p.SetAssetEnabled(asset.Spot, true)
if err == nil {
t.Fatal("unexpected result")
}
// Test asset type which doesn't exist
p = initTest(t)
p.Pairs[asset.Spot].AssetEnabled = nil
err = p.IsAssetEnabled(asset.Spot)
if err == nil {
t.Error("unexpected result")
}
err = p.SetAssetEnabled(asset.Spot, false)
if err != nil {
t.Fatal(err)
}
err = p.SetAssetEnabled(asset.Spot, false)
if err == nil {
t.Fatal("unexpected result")
}
err = p.IsAssetEnabled(asset.Spot)
if err == nil {
t.Error("unexpected result")
}
err = p.SetAssetEnabled(asset.Spot, true)
if err != nil {
t.Fatal(err)
}
err = p.SetAssetEnabled(asset.Spot, true)
if err == nil {
t.Fatal("unexpected result")
}
err = p.IsAssetEnabled(asset.Spot)
if err != nil {
t.Error("unexpected result")
}
}
// TestFullStoreUnmarshalMarshal tests json Mashal and Unmarshal
func TestFullStoreUnmarshalMarshal(t *testing.T) {
t.Parallel()
var um = make(FullStore)
um[asset.Spot] = &PairStore{AssetEnabled: convert.BoolPtr(true)}
data, err := json.Marshal(um)
if err != nil {
t.Fatal(err)
}
if string(data) != `{"spot":{"assetEnabled":true,"enabled":"","available":""}}` {
t.Fatal("unexpected value")
}
var another FullStore
err = json.Unmarshal(data, &another)
if !errors.Is(err, nil) {
t.Fatalf("received: '%v' but expected: '%v'", err, nil)
}
if _, ok := another[asset.Spot]; !ok {
t.Fatal("expected values to be associated with spot")
}
data = []byte(`{123:{"assetEnabled":null,"enabled":"","available":""}}`)
err = json.Unmarshal(data, &another)
if errors.Is(err, nil) {
t.Fatalf("expected error")
}
data = []byte(`{"bro":{"assetEnabled":null,"enabled":"","available":""}}`)
err = json.Unmarshal(data, &another)
if !errors.Is(err, asset.ErrNotSupported) {
t.Fatalf("received: '%v' but expected: '%v'", err, asset.ErrNotSupported)
}
}
func TestIsPairEnabled(t *testing.T) {
t.Parallel()
pm := initTest(t)
cp := NewPairWithDelimiter("BTC", "USD", "-")
enabled, err := pm.IsPairEnabled(cp, asset.Spot)
if !errors.Is(err, nil) {
t.Fatalf("received: '%v' but expected: '%v'", err, nil)
}
if !enabled {
t.Fatal("expected pair to be enabled")
}
enabled, err = pm.IsPairEnabled(NewPair(SAFE, MOONRISE), asset.Spot)
if !errors.Is(err, nil) {
t.Fatalf("received: '%v' but expected: '%v'", err, nil)
}
if enabled {
t.Fatal("expected pair to be disabled")
}
enabled, err = pm.IsPairEnabled(cp, asset.Futures)
if !errors.Is(err, nil) {
t.Fatalf("received: '%v' but expected: '%v'", err, nil)
}
if enabled {
t.Fatal("expected pair to be disabled because asset type is not enabled")
}
cp = NewPairWithDelimiter("XRP", "DOGE", "-")
enabled, err = pm.IsPairEnabled(cp, asset.Spot)
if !errors.Is(err, nil) {
t.Fatalf("received: '%v' but expected: '%v'", err, nil)
}
if enabled {
t.Fatal("expected pair to be disabled because pair not found in enabled list")
}
_, err = pm.IsPairEnabled(cp, asset.PerpetualSwap)
if !errors.Is(err, ErrAssetNotFound) {
t.Fatalf("received: '%v' but expected: '%v'", err, ErrAssetNotFound)
}
_, err = pm.IsPairEnabled(cp, asset.Item(1337))
if !errors.Is(err, asset.ErrNotSupported) {
t.Fatalf("received: '%v' but expected: '%v'", err, asset.ErrNotSupported)
}
pm.Pairs[asset.PerpetualSwap] = &PairStore{}
_, err = pm.IsPairEnabled(cp, asset.PerpetualSwap)
if !errors.Is(err, ErrAssetIsNil) {
t.Fatalf("received: '%v' but expected: '%v'", err, ErrAssetIsNil)
}
_, err = pm.IsPairEnabled(EMPTYPAIR, asset.PerpetualSwap)
if !errors.Is(err, ErrCurrencyPairEmpty) {
t.Fatalf("received: '%v' but expected: '%v'", err, ErrCurrencyPairEmpty)
}
}
func TestEnsureOnePairEnabled(t *testing.T) {
t.Parallel()
p := NewPair(BTC, USDT)
pm := PairsManager{
Pairs: map[asset.Item]*PairStore{
asset.Futures: {},
asset.Spot: {
AssetEnabled: convert.BoolPtr(true),
Available: []Pair{
p,
},
},
},
}
pair, item, err := pm.EnsureOnePairEnabled()
if !errors.Is(err, nil) {
t.Errorf("received: '%v' but expected: '%v'", err, nil)
}
if len(pm.Pairs[asset.Spot].Enabled) != 1 {
t.Errorf("received: '%v' but expected: '%v'", len(pm.Pairs[asset.Spot].Enabled), 1)
}
if item != asset.Spot || !pair.Equal(p) {
t.Errorf("received: '%v %v' but expected: '%v %v'", item, p, asset.Spot, p)
}
pair, item, err = pm.EnsureOnePairEnabled()
if !errors.Is(err, nil) {
t.Errorf("received: '%v' but expected: '%v'", err, nil)
}
if len(pm.Pairs[asset.Spot].Enabled) != 1 {
t.Errorf("received: '%v' but expected: '%v'", len(pm.Pairs[asset.Spot].Enabled), 1)
}
if item != asset.Empty || !pair.Equal(EMPTYPAIR) {
t.Errorf("received: '%v %v' but expected: '%v %v'", item, p, asset.Empty, EMPTYPAIR)
}
pm = PairsManager{
Pairs: map[asset.Item]*PairStore{
asset.Futures: {
AssetEnabled: convert.BoolPtr(true),
Available: []Pair{
NewPair(BTC, USDC),
},
},
asset.Spot: {
AssetEnabled: convert.BoolPtr(true),
Enabled: []Pair{
p,
},
Available: []Pair{
p,
},
},
},
}
pair, item, err = pm.EnsureOnePairEnabled()
if !errors.Is(err, nil) {
t.Errorf("received: '%v' but expected: '%v'", err, nil)
}
if len(pm.Pairs[asset.Spot].Enabled) != 1 {
t.Errorf("received: '%v' but expected: '%v'", len(pm.Pairs[asset.Spot].Enabled), 1)
}
if len(pm.Pairs[asset.Futures].Enabled) != 0 {
t.Errorf("received: '%v' but expected: '%v'", len(pm.Pairs[asset.Futures].Enabled), 0)
}
if item != asset.Empty || !pair.Equal(EMPTYPAIR) {
t.Errorf("received: '%v %v' but expected: '%v %v'", item, p, asset.Empty, EMPTYPAIR)
}
pm = PairsManager{
Pairs: map[asset.Item]*PairStore{
asset.Futures: {
AssetEnabled: convert.BoolPtr(true),
Available: []Pair{p},
},
asset.Options: {
AssetEnabled: convert.BoolPtr(true),
Available: []Pair{},
},
},
}
pair, item, err = pm.EnsureOnePairEnabled()
if !errors.Is(err, nil) {
t.Errorf("received: '%v' but expected: '%v'", err, nil)
}
if len(pm.Pairs[asset.Futures].Enabled) != 1 {
t.Errorf("received: '%v' but expected: '%v'", len(pm.Pairs[asset.Futures].Enabled), 1)
}
if item != asset.Futures || !pair.Equal(p) {
t.Errorf("received: '%v %v' but expected: '%v %v'", item, p, asset.Futures, p)
}
pm = PairsManager{
Pairs: map[asset.Item]*PairStore{},
}
_, _, err = pm.EnsureOnePairEnabled()
if !errors.Is(err, ErrCurrencyPairsEmpty) {
t.Errorf("received: '%v' but expected: '%v'", err, ErrCurrencyPairsEmpty)
}
}
func TestLoad(t *testing.T) {
t.Parallel()
base := PairsManager{}
fmt1 := &PairFormat{Uppercase: true}
fmt2 := &PairFormat{Uppercase: true, Delimiter: DashDelimiter}
p := NewPair(BTC, USDT)
tt := int64(1337)
seed := PairsManager{
LastUpdated: tt,
UseGlobalFormat: true,
ConfigFormat: fmt1,
RequestFormat: fmt2,
Pairs: map[asset.Item]*PairStore{
asset.Futures: {
AssetEnabled: convert.BoolPtr(true),
Available: []Pair{p},
},
asset.Options: {
AssetEnabled: convert.BoolPtr(false),
Available: []Pair{},
},
},
}
assert.ErrorIs(t, base.Load(nil), common.ErrNilPointer, "Load nil should error")
if assert.NoError(t, base.Load(&seed), "Loading from seed should not error") {
assert.True(t, *base.Pairs[asset.Futures].AssetEnabled, "Futures AssetEnabled should be true")
assert.True(t, base.Pairs[asset.Futures].Available.Contains(p, true), "Futures Available Pairs should contain BTCUSDT")
assert.False(t, *base.Pairs[asset.Options].AssetEnabled, "Options AssetEnabled should be false")
assert.Equal(t, tt, base.LastUpdated, "Last Updated should be correct")
assert.Equal(t, fmt1.Uppercase, base.ConfigFormat.Uppercase, "ConfigFormat Uppercase should be correct")
assert.Equal(t, fmt2.Delimiter, base.RequestFormat.Delimiter, "RequestFormat Delimiter should be correct")
}
}
func checkPairDelimiter(tb testing.TB, p *PairsManager, err error, d, msg string) {
tb.Helper()
if assert.NoError(tb, err, "UnmarshalJSON should not error") {
err := p.SetDelimitersFromConfig()
assert.NoError(tb, err, "SetDelimitersFromConfig should not error")
s := p.Pairs[asset.Spot]
if assert.NotNil(tb, s, "Spot asset should not be nil") {
for _, ps := range []Pairs{s.Enabled, s.Available} {
if assert.NotEmpty(tb, ps, "PairStore should not be empty") {
for _, p := range ps {
assert.Equalf(tb, d, p.Delimiter, msg)
}
}
}
}
}
}
// TestPairManagerUnmarshal tests behaviour expectations:
// * Should error with no ConfigFormat ( `CheckPairConsistency` catches that )
// * Asset pair config should take precedent
// * Global store pair config should apply when no Asset pair config
func TestPairManagerSetDelimitersFromConfig(t *testing.T) {
t.Parallel()
p := new(PairsManager)
err := json.Unmarshal([]byte(`{"pairs":{"spot":{"enabled":"BTC-USD,M_PIT-USDT","available":"BTC-USD,M_PIT-USD"}}}`), p)
if assert.NoError(t, err, "UnmarshalJSON should not error") {
err = p.SetDelimitersFromConfig()
assert.ErrorIs(t, err, errPairConfigFormatNil, "SetDelimitersFromConfig should error correctly")
}
err = json.Unmarshal([]byte(`{"pairs":{"spot":{"configFormat":{"delimiter":"-"},"enabled":"BTC-USD,M_PIT-USDT","available":"BTC-USD,M_PIT-USD"}}}`), p)
checkPairDelimiter(t, p, err, "-", "Delimiter should be correct with only bottom level configFormat")
err = json.Unmarshal([]byte(`{"configFormat":{"delimiter":"/"},"pairs":{"spot":{"enabled":"BTC/USD,M_PIT/USDT","available":"BTC/USD,M_PIT/USD"}}}`), p)
checkPairDelimiter(t, p, err, "/", "Delimiter should be correct with top level configFormat")
err = json.Unmarshal([]byte(`{"configFormat":{"delimiter":"_"},"pairs":{"spot":{"configFormat":{"delimiter":"/"},"enabled":"BTC/USD,M_PIT/USDT","available":"BTC/USD,M_PIT/USD"}}}`), p)
checkPairDelimiter(t, p, err, "/", "Delimiter should be correct with bottom level configFormat")
err = json.Unmarshal([]byte(`{"pairs":{"spot":{"configFormat":{"delimiter":"_"},"enabled":"BTC-USDT","available":"BTC-USDT"}}}`), p)
if assert.NoError(t, err, "UnmarshalJSON should not error") {
err := p.SetDelimitersFromConfig()
assert.ErrorContains(t, err, "spot.enabled.BTC-USDT: delimiter: [_] not found in currencypair string", "SetDelimitersFromConfig should error correctly")
}
}