Files
gocryptotrader/exchanges/kline/request_test.go
Scott fcc5ad4551 exchanges/qa: Add exchange wrapper testing suite (#1159)
* initial concept of a nice validation tester for exchanges

* adds some datahandler design

* expand testing

* more tests and fixes

* minor end of day fix for bithumb

* fixes implementation issues

* more test coverage and improvements, but not sure if i should continue

* fix more wrapper implementations

* adds error type, more fixes

* changes signature, fixes implementations

* fixes more wrapper implementations

* one more bit

* more cleanup

* WOW things work?

* lintle 1/1337

* mini bump

* fixes all linting

* neaten

* GetOrderInfo+ asset pair fixes+improvements

* adds new websocket test

* expand ws testing

* fix bug, expand tests, improve implementation

* code coverage of a lot of new codes

* fixes everything

* reverts accidental changes

* minor fixes from reviewing code

* removes Bitfinex cancelBatchOrder implementation

* fixes dumb baby typo for babies

* mini nit fixes

* so many nits to address

* addresses all the nits

* Titlecase

* switcheroo

* removes websocket testing for now

* fix appveyor, minor test fix

* fixes typo, re-kindles killed kode

* skip binance wrapper tests when running CI

* expired context, huobi okx fixes

* kodespull

* fix ordering

* time fix because why not

* fix exmo, others

* hopefully this fixes all of my life's problems

* last thing today

* huobi, more like hypotrophy

* golangci-lint, more like mypooroldknee-splint

* fix huobi times by removing them

* should fix okx currency issues

* blocks the application

* adds last little contingency for pairs

* addresses most nits and new problems

* lovely fixed before seeing why okx sucks

* fixes issues with okx websocket

* the classic receieieivaier

* lintle

* adds test and fixes existing tests

* expands error handling messages during setup

* fixes dumb okx bugs introduced

* quick fix for lint and exmo

* fixes nixes

* fix exmo deposit issue

* lint

* fixes issue with extra asset runs missing

* fix surprise race

* all the lint and merge fixes

* fixes surprise bugs in OKx

* fixes issues with times and chains

* fixing all the merge stuff

* merge fix

* rm logs and a panic potential

* lovely lint lament

* an easy demonstration of scenario, but not of initial purpose

* put it in the bin

* Revert "put it in the bin"

This reverts commit 15c6490f713233d43f10957367fcbf18e3818bdd.

* re-add after immediate error popup

* fix mini poor test design

* okx okay

* merge fixes

* fixes issues discovered in lovely test

* I FORGOT TO COMMIT THIS

* nit fixaroonaboo

* forgoetten test fix

* revert old okx asset intrument work

* fixes

* revert problems I didnt understand. update bybit

* fix merge bugs

* test cleanup

* further improvements

* reshuffle and lint

* rm redundant CI_TEST by rm the CI_TEST field that is redundant

* path fix

* move to its own section, dont run on 32 bit + appveyor

* lint

* fix lbank

* address nits

* let it rip

* fix failing test time range

* niteroo boogaloo

* mod tidy, use common.SimpleTimeFormat
2023-07-03 11:09:43 +10:00

427 lines
13 KiB
Go

package kline
import (
"errors"
"sync"
"testing"
"time"
"github.com/thrasher-corp/gocryptotrader/common"
"github.com/thrasher-corp/gocryptotrader/currency"
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
)
func TestCreateKlineRequest(t *testing.T) {
t.Parallel()
_, err := CreateKlineRequest("", currency.EMPTYPAIR, currency.EMPTYPAIR, 0, 0, 0, time.Time{}, time.Time{}, 0)
if !errors.Is(err, ErrUnsetName) {
t.Fatalf("received: '%v', but expected '%v'", err, ErrUnsetName)
}
_, err = CreateKlineRequest("name", currency.EMPTYPAIR, currency.EMPTYPAIR, 0, 0, 0, time.Time{}, time.Time{}, 0)
if !errors.Is(err, currency.ErrCurrencyPairEmpty) {
t.Fatalf("received: '%v', but expected '%v'", err, currency.ErrCurrencyPairEmpty)
}
pair := currency.NewPair(currency.BTC, currency.USDT)
_, err = CreateKlineRequest("name", pair, currency.EMPTYPAIR, 0, 0, 0, time.Time{}, time.Time{}, 0)
if !errors.Is(err, currency.ErrCurrencyPairEmpty) {
t.Fatalf("received: '%v', but expected '%v'", err, currency.ErrCurrencyPairEmpty)
}
pair2 := pair.Upper()
_, err = CreateKlineRequest("name", pair, pair2, 0, 0, 0, time.Time{}, time.Time{}, 0)
if !errors.Is(err, asset.ErrNotSupported) {
t.Fatalf("received: '%v', but expected '%v'", err, asset.ErrNotSupported)
}
_, err = CreateKlineRequest("name", pair, pair2, asset.Spot, 0, 0, time.Time{}, time.Time{}, 0)
if !errors.Is(err, ErrInvalidInterval) {
t.Fatalf("received: '%v', but expected '%v'", err, ErrInvalidInterval)
}
_, err = CreateKlineRequest("name", pair, pair2, asset.Spot, OneHour, 0, time.Time{}, time.Time{}, 0)
if !errors.Is(err, ErrInvalidInterval) {
t.Fatalf("received: '%v', but expected '%v'", err, ErrInvalidInterval)
}
_, err = CreateKlineRequest("name", pair, pair2, asset.Spot, OneHour, OneMin, time.Time{}, time.Time{}, 0)
if !errors.Is(err, common.ErrDateUnset) {
t.Fatalf("received: '%v', but expected '%v'", err, common.ErrDateUnset)
}
start := time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC)
_, err = CreateKlineRequest("name", pair, pair2, asset.Spot, OneHour, OneMin, start, time.Time{}, 0)
if !errors.Is(err, common.ErrDateUnset) {
t.Fatalf("received: '%v', but expected '%v'", err, common.ErrDateUnset)
}
end := start.AddDate(0, 0, 1)
_, err = CreateKlineRequest("name", pair, pair2, asset.Spot, OneHour, OneMin, start, end, 0)
if !errors.Is(err, errInvalidSpecificEndpointLimit) {
t.Fatalf("received: '%v', but expected '%v'", err, errInvalidSpecificEndpointLimit)
}
r, err := CreateKlineRequest("name", pair, pair2, asset.Spot, OneHour, OneMin, start, end, 1)
if !errors.Is(err, nil) {
t.Fatalf("received: '%v', but expected '%v'", err, nil)
}
if r.Exchange != "name" {
t.Fatalf("received: '%v' but expected: '%v'", r.Exchange, "name")
}
if !r.Pair.Equal(pair) {
t.Fatalf("received: '%v' but expected: '%v'", r.Pair, pair)
}
if r.Asset != asset.Spot {
t.Fatalf("received: '%v' but expected: '%v'", r.Asset, asset.Spot)
}
if r.ExchangeInterval != OneMin {
t.Fatalf("received: '%v' but expected: '%v'", r.ExchangeInterval, OneMin)
}
if r.ClientRequired != OneHour {
t.Fatalf("received: '%v' but expected: '%v'", r.ClientRequired, OneHour)
}
if r.Start != start {
t.Fatalf("received: '%v' but expected: '%v'", r.Start, start)
}
if r.End != end {
t.Fatalf("received: '%v' but expected: '%v'", r.End, end)
}
if r.RequestFormatted.String() != "BTCUSDT" {
t.Fatalf("received: '%v' but expected: '%v'", r.RequestFormatted.String(), "BTCUSDT")
}
// Check end date/time shift if the request time is mid candle and not
// aligned correctly.
end = end.Round(0)
end = end.Add(time.Second * 30)
r, err = CreateKlineRequest("name", pair, pair2, asset.Spot, OneHour, OneMin, start, end, 1)
if !errors.Is(err, nil) {
t.Fatalf("received: '%v', but expected '%v'", err, nil)
}
if !r.End.Equal(end.Add(OneHour.Duration() - (time.Second * 30))) {
t.Fatalf("received: '%v', but expected '%v'", r.End, end.Add(OneHour.Duration()-(time.Second*30)))
}
}
func TestGetRanges(t *testing.T) {
t.Parallel()
start := time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC)
end := start.AddDate(0, 0, 1)
pair := currency.NewPair(currency.BTC, currency.USDT)
var r *Request
_, err := r.GetRanges(100)
if !errors.Is(err, errNilRequest) {
t.Fatalf("received: '%v', but expected '%v'", err, errNilRequest)
}
r, err = CreateKlineRequest("name", pair, pair, asset.Spot, OneHour, OneMin, start, end, 1)
if !errors.Is(err, nil) {
t.Fatalf("received: '%v', but expected '%v'", err, nil)
}
holder, err := r.GetRanges(100)
if !errors.Is(err, nil) {
t.Fatalf("received: '%v', but expected '%v'", err, nil)
}
if len(holder.Ranges) != 15 {
t.Fatalf("received: '%v', but expected '%v'", len(holder.Ranges), 15)
}
}
var protecThyCandles sync.Mutex
func getOneMinute() []Candle {
protecThyCandles.Lock()
candles := make([]Candle, len(oneMinuteCandles))
copy(candles, oneMinuteCandles)
protecThyCandles.Unlock()
return candles
}
var oneMinuteCandles = func() []Candle {
var candles []Candle
start := time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC)
for x := 0; x < 1442; x++ { // two extra candles.
candles = append(candles, Candle{
Time: start,
Volume: 1,
Open: 1,
High: float64(1 + x),
Low: float64(-(1 + x)),
Close: 1,
})
start = start.Add(time.Minute)
}
return candles
}()
func getOneHour() []Candle {
protecThyCandles.Lock()
candles := make([]Candle, len(oneHourCandles))
copy(candles, oneHourCandles)
protecThyCandles.Unlock()
return candles
}
var oneHourCandles = func() []Candle {
var candles []Candle
start := time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC)
for x := 0; x < 24; x++ {
candles = append(candles, Candle{
Time: start,
Volume: 1,
Open: 1,
High: float64(1 + x),
Low: float64(-(1 + x)),
Close: 1,
})
start = start.Add(time.Hour)
}
return candles
}()
func TestRequest_ProcessResponse(t *testing.T) {
t.Parallel()
start := time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC)
end := start.AddDate(0, 0, 1)
pair := currency.NewPair(currency.BTC, currency.USDT)
var r *Request
_, err := r.ProcessResponse(nil)
if !errors.Is(err, errNilRequest) {
t.Fatalf("received: '%v', but expected '%v'", err, errNilRequest)
}
r = &Request{}
_, err = r.ProcessResponse(nil)
if !errors.Is(err, ErrNoTimeSeriesDataToConvert) {
t.Fatalf("received: '%v', but expected '%v'", err, ErrNoTimeSeriesDataToConvert)
}
_, err = CreateKlineRequest("name", pair, pair, asset.Spot, OneHour, OneHour, start, end, 0)
if !errors.Is(err, errInvalidSpecificEndpointLimit) {
t.Fatalf("received: '%v', but expected '%v'", err, nil)
}
// no conversion
r, err = CreateKlineRequest("name", pair, pair, asset.Spot, OneHour, OneHour, start, end, 1)
if !errors.Is(err, nil) {
t.Fatalf("received: '%v', but expected '%v'", err, nil)
}
holder, err := r.ProcessResponse(getOneHour())
if !errors.Is(err, nil) {
t.Fatalf("received: '%v', but expected '%v'", err, nil)
}
if len(holder.Candles) != 24 {
t.Fatalf("received: '%v', but expected '%v'", len(holder.Candles), 24)
}
// with conversion
r, err = CreateKlineRequest("name", pair, pair, asset.Spot, OneHour, OneMin, start, end, 1)
if !errors.Is(err, nil) {
t.Fatalf("received: '%v', but expected '%v'", err, nil)
}
holder, err = r.ProcessResponse(getOneMinute())
if !errors.Is(err, nil) {
t.Fatalf("received: '%v', but expected '%v'", err, nil)
}
if len(holder.Candles) != 24 {
t.Fatalf("received: '%v', but expected '%v'", len(holder.Candles), 24)
}
// Potential partial candle
end = time.Now().UTC()
start = end.AddDate(0, 0, -5).Truncate(time.Duration(OneDay))
r, err = CreateKlineRequest("name", pair, pair, asset.Spot, OneDay, OneDay, start, end, 1)
if !errors.Is(err, nil) {
t.Fatalf("received: '%v', but expected '%v'", err, nil)
}
if !r.PartialCandle {
t.Fatalf("received: '%v', but expected '%v'", r.PartialCandle, true)
}
hasIncomplete := []Candle{
{Time: start, Close: 1},
{Time: start.Add(OneDay.Duration()), Close: 2},
{Time: start.Add(OneDay.Duration() * 2), Close: 3},
{Time: start.Add(OneDay.Duration() * 3), Close: 4},
{Time: start.Add(OneDay.Duration() * 4), Close: 5},
{Time: start.Add(OneDay.Duration() * 5), Close: 5.5},
}
sweetItem, err := r.ProcessResponse(hasIncomplete)
if !errors.Is(err, nil) {
t.Fatalf("received: '%v', but expected '%v'", err, nil)
}
if sweetItem.Candles[len(sweetItem.Candles)-1].ValidationIssues != PartialCandle {
t.Fatalf("received: '%v', but expected '%v'", "no issues", PartialCandle)
}
missingIncomplete := []Candle{
{Time: start, Close: 1},
{Time: start.Add(OneDay.Duration()), Close: 2},
{Time: start.Add(OneDay.Duration() * 2), Close: 3},
{Time: start.Add(OneDay.Duration() * 3), Close: 4},
{Time: start.Add(OneDay.Duration() * 4), Close: 5},
}
sweetItem, err = r.ProcessResponse(missingIncomplete)
if !errors.Is(err, nil) {
t.Fatalf("received: '%v', but expected '%v'", err, nil)
}
if sweetItem.Candles[len(sweetItem.Candles)-1].ValidationIssues == PartialCandle {
t.Fatalf("received: '%v', but expected '%v'", sweetItem.Candles[len(sweetItem.Candles)-1].ValidationIssues, "no issues")
}
// end date far into the dark depths of future reality
r, err = CreateKlineRequest("name", pair, pair, asset.Spot, OneDay, OneDay, start, end.AddDate(1, 0, 0), 1)
if !errors.Is(err, nil) {
t.Fatalf("received: '%v', but expected '%v'", err, nil)
}
sweetItem, err = r.ProcessResponse(hasIncomplete)
if !errors.Is(err, nil) {
t.Fatalf("received: '%v', but expected '%v'", err, nil)
}
if sweetItem.Candles[len(sweetItem.Candles)-1].ValidationIssues != PartialCandle {
t.Fatalf("received: '%v', but expected '%v'", "no issues", PartialCandle)
}
sweetItem, err = r.ProcessResponse(missingIncomplete)
if !errors.Is(err, nil) {
t.Fatalf("received: '%v', but expected '%v'", err, nil)
}
if len(sweetItem.Candles) != 5 {
t.Fatalf("received: '%v', but expected '%v'", len(sweetItem.Candles), 5)
}
if sweetItem.Candles[len(sweetItem.Candles)-1].ValidationIssues == PartialCandle {
t.Fatalf("received: '%v', but expected '%v'", sweetItem.Candles[len(sweetItem.Candles)-1].ValidationIssues, "no issues")
}
laterEndDate := end.AddDate(1, 0, 0).UTC().Truncate(time.Duration(OneDay)).Add(-time.Duration(OneDay))
if sweetItem.Candles[len(sweetItem.Candles)-1].Time.Equal(laterEndDate) {
t.Fatalf("received: '%v', but expected '%v'", sweetItem.Candles[len(sweetItem.Candles)-1].Time, "should not equal")
}
}
func TestExtendedRequest_ProcessResponse(t *testing.T) {
t.Parallel()
ohc := getOneHour()
start := ohc[0].Time
end := ohc[len(ohc)-1].Time.Add(OneHour.Duration())
pair := currency.NewPair(currency.BTC, currency.USDT)
var rExt *ExtendedRequest
_, err := rExt.ProcessResponse(nil)
if !errors.Is(err, errNilRequest) {
t.Fatalf("received: '%v', but expected '%v'", err, errNilRequest)
}
rExt = &ExtendedRequest{}
_, err = rExt.ProcessResponse(nil)
if !errors.Is(err, ErrNoTimeSeriesDataToConvert) {
t.Fatalf("received: '%v', but expected '%v'", err, ErrNoTimeSeriesDataToConvert)
}
// no conversion
r, err := CreateKlineRequest("name", pair, pair, asset.Spot, OneHour, OneHour, start, end, 1)
if !errors.Is(err, nil) {
t.Fatalf("received: '%v', but expected '%v'", err, nil)
}
r.ProcessedCandles = ohc
dates, err := r.GetRanges(100)
if !errors.Is(err, nil) {
t.Fatalf("received: '%v', but expected '%v'", err, nil)
}
rExt = &ExtendedRequest{r, dates}
holder, err := rExt.ProcessResponse(ohc)
if !errors.Is(err, nil) {
t.Fatalf("received: '%v', but expected '%v'", err, nil)
}
if len(holder.Candles) != 24 {
t.Fatalf("received: '%v', but expected '%v'", len(holder.Candles), 24)
}
// with conversion
ohc = getOneMinute()
r, err = CreateKlineRequest("name", pair, pair, asset.Spot, OneHour, OneMin, start, end, 1)
if !errors.Is(err, nil) {
t.Fatalf("received: '%v', but expected '%v'", err, nil)
}
dates, err = r.GetRanges(100)
if !errors.Is(err, nil) {
t.Fatalf("received: '%v', but expected '%v'", err, nil)
}
r.IsExtended = true
rExt = &ExtendedRequest{r, dates}
holder, err = rExt.ProcessResponse(ohc)
if !errors.Is(err, nil) {
t.Fatalf("received: '%v', but expected '%v'", err, nil)
}
if len(holder.Candles) != 24 {
t.Fatalf("received: '%v', but expected '%v'", len(holder.Candles), 24)
}
}
func TestExtendedRequest_Size(t *testing.T) {
t.Parallel()
var rExt *ExtendedRequest
if rExt.Size() != 0 {
t.Fatalf("received: '%v', but expected '%v'", rExt.Size(), 0)
}
rExt = &ExtendedRequest{RangeHolder: &IntervalRangeHolder{Limit: 100, Ranges: []IntervalRange{{}, {}}}}
if rExt.Size() != 200 {
t.Fatalf("received: '%v', but expected '%v'", rExt.Size(), 200)
}
}
func TestRequest_Size(t *testing.T) {
t.Parallel()
var r *Request
if r.Size() != 0 {
t.Fatalf("received: '%v', but expected '%v'", r.Size(), 0)
}
r = &Request{
Start: time.Now().Add(-time.Hour * 2).Truncate(time.Hour),
End: time.Now().Truncate(time.Hour),
ExchangeInterval: OneHour,
}
if r.Size() != 2 {
t.Fatalf("received: '%v', but expected '%v'", r.Size(), 2)
}
}