Files
gocryptotrader/currency/manager_test.go
Ryan O'Hara-Reid e99adca86f encoding/json: Add custom JSON package with build tag support for Sonic (#1623)
* tag optional sonic and allow full library conversion

* Add workflow and disallow arm and darwin usage

* Add basic hotswap benchmark

* linter: fix

* use bash

* linter: fix?

* Fix whoopsie, add to make file, also add mention in features list.

* test enforcement

* actually read documentation see if this works

* linter: fix

* linter: fix

* sonic: bump tagged version

* encoding/json: drop build tag arch and os filters

* encoding/json: consolidate tests

* encoding/json: log build tag usage

* rm superfluous builds

* glorious/nits: add template change and regen docs

* glorious/nits: update commentary on nolint directive

* glorious/nits: rm init func and log results in main.go

* Test to actually pull flag in

* linter: fix

* thrasher: nits

* gk: nits 4 goflags goooooooooo!

* gk: nits rn

* make sonic default json implementation

* screen 386

* linter: fix

* Add commentary

* glorious: nits Makefile not working

* gk: nits

* gk: nits whoops

* whoopsirino

* mention 32bit systems won't be sonic

* gk: super-duper nit of extremes

---------

Co-authored-by: Ryan O'Hara-Reid <ryan.oharareid@thrasher.io>
2025-02-20 16:05:55 +11:00

919 lines
25 KiB
Go

package currency
import (
"errors"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/thrasher-corp/gocryptotrader/common/convert"
"github.com/thrasher-corp/gocryptotrader/encoding/json"
"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)
err := p.DisablePair(asset.Empty, EMPTYPAIR)
assert.ErrorIs(t, err, asset.ErrNotSupported, "Empty asset should error")
err = p.DisablePair(asset.Spot, EMPTYPAIR)
assert.ErrorIs(t, err, ErrCurrencyPairEmpty, "Empty pair should error")
p.Pairs = nil
err = p.DisablePair(asset.Spot, NewPair(BTC, USD))
assert.ErrorIs(t, err, ErrPairManagerNotInitialised, "Uninitialised PairManager should error")
p = initTest(t)
err = p.DisablePair(asset.CoinMarginedFutures, EMPTYPAIR)
assert.ErrorIs(t, err, ErrCurrencyPairEmpty, "Non-existent asset type should error")
p.Pairs[asset.Spot] = nil
err = p.DisablePair(asset.Spot, EMPTYPAIR)
assert.ErrorIs(t, err, ErrCurrencyPairEmpty, "Empty pair store should error")
p = initTest(t)
err = p.DisablePair(asset.Spot, NewPair(LTC, USD))
assert.ErrorIs(t, err, ErrPairNotFound, "Not Enabled pair should error")
err = p.DisablePair(asset.Spot, NewPair(BTC, USD))
assert.NoError(t, err, "DisablePair should not error")
}
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 TestIsPairAvailable(t *testing.T) {
t.Parallel()
pm := initTest(t)
cp := NewPairWithDelimiter("BTC", "USD", "-")
ok, err := pm.IsPairAvailable(cp, asset.Spot)
require.NoError(t, err, "IsPairAvailable must not error")
assert.True(t, ok, "IsPairAvailable should return correct value for an available and enabled pair")
ok, err = pm.IsPairAvailable(NewPair(SAFE, MOONRISE), asset.Spot)
require.NoError(t, err, "IsPairAvailable must not error")
assert.False(t, ok, "IsPairAvailable should return correct value for an non-existent")
ok, err = pm.IsPairAvailable(cp, asset.Futures)
require.NoError(t, err, "IsPairAvailable must not error")
assert.False(t, ok, "IsPairAvailable should return false for a disabled asset type")
cp = NewPairWithDelimiter("XRP", "DOGE", "-")
ok, err = pm.IsPairAvailable(cp, asset.Spot)
require.NoError(t, err, "IsPairAvailable must not error")
assert.False(t, ok, "IsPairAvailable should return false for non-existent pair")
_, err = pm.IsPairAvailable(cp, asset.PerpetualSwap)
assert.ErrorIs(t, err, ErrAssetNotFound, "Should error when asset is not found")
_, err = pm.IsPairAvailable(cp, asset.Item(1337))
assert.ErrorIs(t, err, asset.ErrNotSupported, "Should error when asset is not supported")
pm.Pairs[asset.PerpetualSwap] = &PairStore{}
_, err = pm.IsPairAvailable(cp, asset.PerpetualSwap)
assert.ErrorIs(t, err, ErrAssetIsNil, "Should error when store AssetEnabled is nil")
_, err = pm.IsPairAvailable(EMPTYPAIR, asset.PerpetualSwap)
assert.ErrorIs(t, err, ErrCurrencyPairEmpty, "Should error when currency pair is empty")
}
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{},
},
},
}
base.Load(&seed)
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")
found, err := base.Match("BTCUSDT", asset.Futures)
require.NoError(t, err, "Match must not error")
assert.Equal(t, p, found, "Should find the right pair")
}
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.ErrorIs(t, err, errDelimiterNotFound, "SetDelimitersFromConfig should error correctly")
}
}
// TestGetFormat exercises PairsManager GetFormat
func TestGetFormat(t *testing.T) {
t.Parallel()
m := PairsManager{
UseGlobalFormat: true,
ConfigFormat: &PairFormat{
Uppercase: true,
Delimiter: "🦄",
},
RequestFormat: &PairFormat{
Delimiter: "~",
},
}
pFmt, err := m.GetFormat(asset.Spot, true)
require.NoError(t, err)
assert.Equal(t, "~", pFmt.Delimiter, "Global Format Delimiter should be correct")
assert.False(t, pFmt.Uppercase, "Global Format Uppercase should be correct")
pFmt, err = m.GetFormat(asset.Spot, false)
require.NoError(t, err)
assert.Equal(t, "🦄", pFmt.Delimiter, "Global Format Delimiter should be special")
assert.True(t, pFmt.Uppercase, "Global Format Uppercase should be correct")
m.ConfigFormat = nil
m.RequestFormat = nil
_, err = m.GetFormat(asset.Spot, true)
assert.ErrorIs(t, err, ErrPairFormatIsNil, "Global GetFormat Should error correctly on nil request format")
_, err = m.GetFormat(asset.Spot, false)
assert.ErrorIs(t, err, ErrPairFormatIsNil, "Global GetFormat Should error correctly on nil config format")
m.UseGlobalFormat = false
err = m.Store(asset.Spot, &PairStore{
ConfigFormat: &pFmt,
RequestFormat: &PairFormat{Delimiter: "/", Uppercase: true},
})
require.NoError(t, err, "Store must not error")
pFmt, err = m.GetFormat(asset.Spot, false)
require.NoError(t, err)
assert.Equal(t, "🦄", pFmt.Delimiter, "Per Asset Format Delimiter should be correct")
assert.True(t, pFmt.Uppercase, "Per Asset Format Uppercase should be correct")
pFmt, err = m.GetFormat(asset.Spot, true)
require.NoError(t, err)
assert.Equal(t, "/", pFmt.Delimiter, "Per Asset Format Delimiter should be correct")
assert.True(t, pFmt.Uppercase, "Per Asset Format Uppercase should be correct")
err = m.Store(asset.Spot, &PairStore{})
require.NoError(t, err, "Store must not error")
_, err = m.GetFormat(asset.Spot, true)
assert.ErrorIs(t, err, ErrPairFormatIsNil, "Per Asset GetFormat Should error correctly on nil request format")
_, err = m.GetFormat(asset.Spot, false)
assert.ErrorIs(t, err, ErrPairFormatIsNil, "Per Asset GetFormat Should error correctly on nil config format")
}
// TestIsAssetSupported exercises IsAssetSupported
func TestIsAssetSupported(t *testing.T) {
t.Parallel()
p := PairsManager{
Pairs: FullStore{
asset.Spot: {
AssetEnabled: convert.BoolPtr(false),
},
},
}
assert.True(t, p.IsAssetSupported(asset.Spot), "Spot should be supported")
assert.False(t, p.IsAssetSupported(asset.Index), "Index should not be supported")
}