Files
gocryptotrader/exchanges/ticker/ticker_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

451 lines
10 KiB
Go

package ticker
import (
"errors"
"log"
"math/rand"
"os"
"strconv"
"sync"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/thrasher-corp/gocryptotrader/currency"
"github.com/thrasher-corp/gocryptotrader/dispatch"
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
)
func TestMain(m *testing.M) {
err := dispatch.Start(1, dispatch.DefaultJobsLimit)
if err != nil {
log.Fatal(err)
}
cpyMux = service.mux
os.Exit(m.Run())
}
var cpyMux *dispatch.Mux
func TestSubscribeTicker(t *testing.T) {
_, err := SubscribeTicker("", currency.EMPTYPAIR, asset.Empty)
if err == nil {
t.Error("error cannot be nil")
}
p := currency.NewPair(currency.BTC, currency.USD)
// force error
service.mux = nil
err = ProcessTicker(&Price{
Pair: p,
ExchangeName: "subscribetest",
AssetType: asset.Spot})
if err == nil {
t.Error("error cannot be nil")
}
sillyP := p
sillyP.Base = currency.GALA_NEO
err = ProcessTicker(&Price{
Pair: sillyP,
ExchangeName: "subscribetest",
AssetType: asset.Spot})
if err == nil {
t.Error("error cannot be nil")
}
sillyP.Quote = currency.AAA
err = ProcessTicker(&Price{
Pair: sillyP,
ExchangeName: "subscribetest",
AssetType: asset.Spot})
if err == nil {
t.Error("error cannot be nil")
}
err = ProcessTicker(&Price{
Pair: sillyP,
ExchangeName: "subscribetest",
AssetType: asset.DownsideProfitContract,
})
if err == nil {
t.Error("error cannot be nil")
}
// reinstate mux
service.mux = cpyMux
err = ProcessTicker(&Price{
Pair: p,
ExchangeName: "subscribetest",
AssetType: asset.Spot})
if err != nil {
t.Fatal(err)
}
_, err = SubscribeTicker("subscribetest", p, asset.Spot)
if err != nil {
t.Error("cannot subscribe to ticker", err)
}
}
func TestSubscribeToExchangeTickers(t *testing.T) {
_, err := SubscribeToExchangeTickers("")
if err == nil {
t.Error("error cannot be nil")
}
p := currency.NewPair(currency.BTC, currency.USD)
err = ProcessTicker(&Price{
Pair: p,
ExchangeName: "subscribeExchangeTest",
AssetType: asset.Spot})
if err != nil {
t.Error(err)
}
_, err = SubscribeToExchangeTickers("subscribeExchangeTest")
if err != nil {
t.Error("error cannot be nil", err)
}
}
func TestGetTicker(t *testing.T) {
newPair, err := currency.NewPairFromStrings("BTC", "USD")
if err != nil {
t.Fatal(err)
}
priceStruct := Price{
Pair: newPair,
Last: 1200,
High: 1298,
Low: 1148,
Bid: 1195,
Ask: 1220,
Volume: 5,
PriceATH: 1337,
ExchangeName: "bitfinex",
AssetType: asset.Spot,
}
err = ProcessTicker(&priceStruct)
if err != nil {
t.Fatal("ProcessTicker error", err)
}
tickerPrice, err := GetTicker("bitfinex", newPair, asset.Spot)
if err != nil {
t.Errorf("Ticker GetTicker init error: %s", err)
}
if !tickerPrice.Pair.Equal(newPair) {
t.Error("ticker tickerPrice.CurrencyPair value is incorrect")
}
_, err = GetTicker("blah", newPair, asset.Spot)
if err == nil {
t.Fatal("TestGetTicker returned nil error on invalid exchange")
}
newPair.Base = currency.ETH
_, err = GetTicker("bitfinex", newPair, asset.Spot)
if err == nil {
t.Fatal("TestGetTicker returned ticker for invalid first currency")
}
btcltcPair, err := currency.NewPairFromStrings("BTC", "LTC")
if err != nil {
t.Fatal(err)
}
_, err = GetTicker("bitfinex", btcltcPair, asset.Spot)
if err == nil {
t.Fatal("TestGetTicker returned ticker for invalid second currency")
}
priceStruct.PriceATH = 9001
priceStruct.Pair.Base = currency.ETH
priceStruct.AssetType = asset.DownsideProfitContract
err = ProcessTicker(&priceStruct)
if err != nil {
t.Fatal("ProcessTicker error", err)
}
tickerPrice, err = GetTicker("bitfinex", newPair, asset.DownsideProfitContract)
if err != nil {
t.Errorf("Ticker GetTicker init error: %s", err)
}
if tickerPrice.PriceATH != 9001 {
t.Error("ticker tickerPrice.PriceATH value is incorrect")
}
_, err = GetTicker("bitfinex", newPair, asset.UpsideProfitContract)
if err == nil {
t.Error("Ticker GetTicker error cannot be nil")
}
priceStruct.AssetType = asset.UpsideProfitContract
err = ProcessTicker(&priceStruct)
if err != nil {
t.Fatal("ProcessTicker error", err)
}
// process update again
err = ProcessTicker(&priceStruct)
if err != nil {
t.Fatal("ProcessTicker error", err)
}
}
func TestFindLast(t *testing.T) {
cp := currency.NewPair(currency.BTC, currency.XRP)
_, err := FindLast(cp, asset.Spot)
if !errors.Is(err, errTickerNotFound) {
t.Errorf("received: %v but expected: %v", err, errTickerNotFound)
}
err = service.update(&Price{Last: 0, ExchangeName: "testerinos", Pair: cp, AssetType: asset.Spot})
if err != nil {
t.Fatal(err)
}
_, err = FindLast(cp, asset.Spot)
if !errors.Is(err, errInvalidTicker) {
t.Errorf("received: %v but expected: %v", err, errInvalidTicker)
}
err = service.update(&Price{Last: 1337, ExchangeName: "testerinos", Pair: cp, AssetType: asset.Spot})
if err != nil {
t.Fatal(err)
}
last, err := FindLast(cp, asset.Spot)
if !errors.Is(err, nil) {
t.Errorf("received: %v but expected: %v", err, nil)
}
if last != 1337 {
t.Fatal("unexpected value")
}
}
func TestProcessTicker(t *testing.T) { // non-appending function to tickers
exchName := "bitstamp"
newPair, err := currency.NewPairFromStrings("BTC", "USD")
if err != nil {
t.Fatal(err)
}
priceStruct := Price{
Last: 1200,
High: 1298,
Low: 1148,
Bid: 1195,
Ask: 1220,
Volume: 5,
PriceATH: 1337,
}
err = ProcessTicker(&priceStruct)
if err == nil {
t.Fatal("empty exchange should throw an err")
}
priceStruct.ExchangeName = exchName
// test for empty pair
err = ProcessTicker(&priceStruct)
if err == nil {
t.Fatal("empty pair should throw an err")
}
// test for empty asset type
priceStruct.Pair = newPair
err = ProcessTicker(&priceStruct)
if err == nil {
t.Fatal("ProcessTicker error cannot be nil")
}
priceStruct.AssetType = asset.Spot
// now process a valid ticker
err = ProcessTicker(&priceStruct)
if err != nil {
t.Fatal("ProcessTicker error", err)
}
result, err := GetTicker(exchName, newPair, asset.Spot)
if err != nil {
t.Fatal("TestProcessTicker failed to create and return a new ticker")
}
if !result.Pair.Equal(newPair) {
t.Fatal("TestProcessTicker pair mismatch")
}
err = ProcessTicker(&Price{
ExchangeName: "Bitfinex",
Pair: currency.NewPair(currency.BTC, currency.USD),
AssetType: asset.Margin,
Bid: 1337,
Ask: 1337,
})
assert.ErrorIs(t, err, ErrBidEqualsAsk, "ProcessTicker should error locked market")
err = ProcessTicker(&Price{
ExchangeName: "Bitfinex",
Pair: currency.NewPair(currency.BTC, currency.USD),
AssetType: asset.Margin,
Bid: 1338,
Ask: 1336,
})
if !errors.Is(err, errBidGreaterThanAsk) {
t.Errorf("received: %v but expected: %v", err, errBidGreaterThanAsk)
}
err = ProcessTicker(&Price{
ExchangeName: "Bitfinex",
Pair: currency.NewPair(currency.BTC, currency.USD),
AssetType: asset.MarginFunding,
Bid: 1338,
Ask: 1336,
})
if !errors.Is(err, nil) {
t.Errorf("received: %v but expected: %v", err, nil)
}
// now test for processing a pair with a different quote currency
newPair, err = currency.NewPairFromStrings("BTC", "AUD")
if err != nil {
t.Fatal(err)
}
priceStruct.Pair = newPair
err = ProcessTicker(&priceStruct)
if err != nil {
t.Fatal("ProcessTicker error", err)
}
_, err = GetTicker(exchName, newPair, asset.Spot)
if err != nil {
t.Fatal("TestProcessTicker failed to create and return a new ticker")
}
_, err = GetTicker(exchName, newPair, asset.Spot)
if err != nil {
t.Fatal("TestProcessTicker failed to return an existing ticker")
}
// now test for processing a pair which has a different base currency
newPair, err = currency.NewPairFromStrings("LTC", "AUD")
if err != nil {
t.Fatal(err)
}
priceStruct.Pair = newPair
err = ProcessTicker(&priceStruct)
if err != nil {
t.Fatal("ProcessTicker error", err)
}
_, err = GetTicker(exchName, newPair, asset.Spot)
if err != nil {
t.Fatal("TestProcessTicker failed to create and return a new ticker")
}
_, err = GetTicker(exchName, newPair, asset.Spot)
if err != nil {
t.Fatal("TestProcessTicker failed to return an existing ticker")
}
type quick struct {
Name string
P currency.Pair
TP Price
}
var testArray []quick
_ = rand.NewSource(time.Now().Unix())
var wg sync.WaitGroup
var sm sync.Mutex
var catastrophicFailure bool
for i := 0; i < 500; i++ {
if catastrophicFailure {
break
}
wg.Add(1)
go func() {
//nolint:gosec // no need to import crypo/rand for testing
newName := "Exchange" + strconv.FormatInt(rand.Int63(), 10)
newPairs, err := currency.NewPairFromStrings("BTC"+strconv.FormatInt(rand.Int63(), 10), //nolint:gosec // no need to import crypo/rand for testing
"USD"+strconv.FormatInt(rand.Int63(), 10)) //nolint:gosec // no need to import crypo/rand for testing
if err != nil {
log.Fatal(err)
}
tp := Price{
Pair: newPairs,
Last: rand.Float64(), //nolint:gosec // no need to import crypo/rand for testing
ExchangeName: newName,
AssetType: asset.Spot,
}
sm.Lock()
err = ProcessTicker(&tp)
if err != nil {
t.Error(err)
catastrophicFailure = true
return
}
testArray = append(testArray, quick{Name: newName, P: newPairs, TP: tp})
sm.Unlock()
wg.Done()
}()
}
if catastrophicFailure {
t.Fatal("ProcessTicker error")
}
wg.Wait()
for _, test := range testArray {
wg.Add(1)
fatalErr := false
go func(test quick) {
result, err := GetTicker(test.Name, test.P, asset.Spot)
if err != nil {
fatalErr = true
return
}
if result.Last != test.TP.Last {
t.Error("TestProcessTicker failed bad values")
}
wg.Done()
}(test)
if fatalErr {
t.Fatal("TestProcessTicker failed to retrieve new ticker")
}
}
wg.Wait()
}
func TestGetAssociation(t *testing.T) {
_, err := service.getAssociations("")
if !errors.Is(err, errExchangeNameIsEmpty) {
t.Errorf("received: %v but expected: %v", err, errExchangeNameIsEmpty)
}
service.mux = nil
_, err = service.getAssociations("getassociation")
if err == nil {
t.Error("error cannot be nil")
}
service.mux = cpyMux
}