Files
gocryptotrader/exchanges/huobi/huobi_test.go
Gareth Kirwan 72f7b3b618 Huobi: Fix TestPairFromContractExpiryCode intermittent failure (#2052)
Huobi decided to not even return CW at all in [this
test](https://github.com/thrasher-corp/gocryptotrader/actions/runs/17637897980/job/50117883059?pr=1990#step:11:1280)

Reverse the test expectation. Just get *something* back in contract
codes. We have to accept that runtime won't work sometimes when we want
to convert codes that they haven't told us about... and that's the way
it is.
2025-09-22 13:40:13 +10:00

2102 lines
67 KiB
Go

package huobi
import (
"errors"
"fmt"
"log"
"os"
"strconv"
"sync"
"testing"
"time"
"github.com/buger/jsonparser"
gws "github.com/gorilla/websocket"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/thrasher-corp/gocryptotrader/common"
"github.com/thrasher-corp/gocryptotrader/common/key"
"github.com/thrasher-corp/gocryptotrader/core"
"github.com/thrasher-corp/gocryptotrader/currency"
"github.com/thrasher-corp/gocryptotrader/exchange/websocket"
exchange "github.com/thrasher-corp/gocryptotrader/exchanges"
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
"github.com/thrasher-corp/gocryptotrader/exchanges/fundingrate"
"github.com/thrasher-corp/gocryptotrader/exchanges/futures"
"github.com/thrasher-corp/gocryptotrader/exchanges/kline"
"github.com/thrasher-corp/gocryptotrader/exchanges/order"
"github.com/thrasher-corp/gocryptotrader/exchanges/orderbook"
"github.com/thrasher-corp/gocryptotrader/exchanges/sharedtestvalues"
"github.com/thrasher-corp/gocryptotrader/exchanges/subscription"
"github.com/thrasher-corp/gocryptotrader/exchanges/ticker"
"github.com/thrasher-corp/gocryptotrader/exchanges/trade"
testexch "github.com/thrasher-corp/gocryptotrader/internal/testing/exchange"
testsubs "github.com/thrasher-corp/gocryptotrader/internal/testing/subscriptions"
mockws "github.com/thrasher-corp/gocryptotrader/internal/testing/websocket"
"github.com/thrasher-corp/gocryptotrader/portfolio/withdraw"
"github.com/thrasher-corp/gocryptotrader/types"
)
// Please supply you own test keys here for due diligence testing.
const (
apiKey = ""
apiSecret = ""
canManipulateRealOrders = false
)
var (
e *Exchange
btcFutureDatedPair currency.Pair
btccwPair = currency.NewPair(currency.BTC, currency.NewCode("CW"))
btcusdPair = currency.NewPairWithDelimiter("BTC", "USD", "-")
btcusdtPair = currency.NewPairWithDelimiter("BTC", "USDT", "-")
ethusdPair = currency.NewPairWithDelimiter("ETH", "USD", "-")
)
func TestMain(m *testing.M) {
e = new(Exchange)
if err := testexch.Setup(e); err != nil {
log.Fatalf("HUOBI Setup error: %s", err)
}
if apiKey != "" && apiSecret != "" {
e.API.AuthenticatedSupport = true
e.API.AuthenticatedWebsocketSupport = true
e.SetCredentials(apiKey, apiSecret, "", "", "", "")
}
os.Exit(m.Run())
}
func TestGetCurrenciesIncludingChains(t *testing.T) {
t.Parallel()
r, err := e.GetCurrenciesIncludingChains(t.Context(), currency.EMPTYCODE)
require.NoError(t, err)
assert.Greater(t, len(r), 1, "should get more than one currency back")
r, err = e.GetCurrenciesIncludingChains(t.Context(), currency.USDT)
require.NoError(t, err)
assert.Equal(t, 1, len(r), "Should only get one currency back")
}
func TestFGetContractInfo(t *testing.T) {
t.Parallel()
_, err := e.FGetContractInfo(t.Context(), "", "", currency.EMPTYPAIR)
require.NoError(t, err)
}
func TestFIndexPriceInfo(t *testing.T) {
t.Parallel()
_, err := e.FIndexPriceInfo(t.Context(), currency.BTC)
require.NoError(t, err)
}
func TestFContractPriceLimitations(t *testing.T) {
t.Parallel()
_, err := e.FContractPriceLimitations(t.Context(),
"BTC", "this_week", currency.EMPTYPAIR)
require.NoError(t, err)
}
func TestFContractOpenInterest(t *testing.T) {
t.Parallel()
_, err := e.FContractOpenInterest(t.Context(), "BTC", "this_week", currency.EMPTYPAIR)
require.NoError(t, err)
}
func TestFGetEstimatedDeliveryPrice(t *testing.T) {
t.Parallel()
_, err := e.FGetEstimatedDeliveryPrice(t.Context(), currency.BTC)
require.NoError(t, err)
}
func TestFGetMarketDepth(t *testing.T) {
t.Parallel()
_, err := e.FGetMarketDepth(t.Context(), btccwPair, "step5")
require.NoError(t, err)
}
func TestFGetKlineData(t *testing.T) {
t.Parallel()
_, err := e.FGetKlineData(t.Context(), btccwPair, "5min", 5, time.Now().Add(-time.Minute*5), time.Now())
require.NoError(t, err)
}
func TestFGetMarketOverviewData(t *testing.T) {
t.Parallel()
_, err := e.FGetMarketOverviewData(t.Context(), btccwPair)
require.NoError(t, err)
}
func TestFLastTradeData(t *testing.T) {
t.Parallel()
_, err := e.FLastTradeData(t.Context(), btccwPair)
require.NoError(t, err)
}
func TestFRequestPublicBatchTrades(t *testing.T) {
t.Parallel()
_, err := e.FRequestPublicBatchTrades(t.Context(), btccwPair, 50)
require.NoError(t, err)
}
func TestFQueryTieredAdjustmentFactor(t *testing.T) {
t.Parallel()
_, err := e.FQueryTieredAdjustmentFactor(t.Context(), currency.BTC)
require.NoError(t, err)
}
func TestFQueryHisOpenInterest(t *testing.T) {
t.Parallel()
_, err := e.FQueryHisOpenInterest(t.Context(), "BTC", "this_week", "60min", "cont", 3)
require.NoError(t, err)
}
func TestFQuerySystemStatus(t *testing.T) {
t.Parallel()
_, err := e.FQuerySystemStatus(t.Context(), currency.BTC)
require.NoError(t, err)
}
func TestFQueryTopAccountsRatio(t *testing.T) {
t.Parallel()
_, err := e.FQueryTopAccountsRatio(t.Context(), "BTC", "5min")
require.NoError(t, err)
}
func TestFQueryTopPositionsRatio(t *testing.T) {
t.Parallel()
_, err := e.FQueryTopPositionsRatio(t.Context(), "BTC", "5min")
require.NoError(t, err)
}
func TestFLiquidationOrders(t *testing.T) {
t.Parallel()
if _, err := e.FLiquidationOrders(t.Context(), currency.BTC, "filled", 0, 0, "", 0); err != nil {
t.Error(err)
}
}
func TestFIndexKline(t *testing.T) {
t.Parallel()
_, err := e.FIndexKline(t.Context(), btccwPair, "5min", 5)
require.NoError(t, err)
}
func TestFGetBasisData(t *testing.T) {
t.Parallel()
_, err := e.FGetBasisData(t.Context(), btccwPair, "5min", "open", 3)
require.NoError(t, err)
}
func TestFGetAccountInfo(t *testing.T) {
t.Parallel()
sharedtestvalues.SkipTestIfCredentialsUnset(t, e)
_, err := e.FGetAccountInfo(t.Context(), currency.EMPTYCODE)
require.NoError(t, err)
}
func TestFGetPositionsInfo(t *testing.T) {
t.Parallel()
sharedtestvalues.SkipTestIfCredentialsUnset(t, e)
_, err := e.FGetPositionsInfo(t.Context(), currency.EMPTYCODE)
require.NoError(t, err)
}
func TestFGetAllSubAccountAssets(t *testing.T) {
t.Parallel()
sharedtestvalues.SkipTestIfCredentialsUnset(t, e)
_, err := e.FGetAllSubAccountAssets(t.Context(), currency.EMPTYCODE)
require.NoError(t, err)
}
func TestFGetSingleSubAccountInfo(t *testing.T) {
t.Parallel()
sharedtestvalues.SkipTestIfCredentialsUnset(t, e)
_, err := e.FGetSingleSubAccountInfo(t.Context(), "", "154263566")
require.NoError(t, err)
}
func TestFGetSingleSubPositions(t *testing.T) {
t.Parallel()
sharedtestvalues.SkipTestIfCredentialsUnset(t, e)
_, err := e.FGetSingleSubPositions(t.Context(), "", "154263566")
require.NoError(t, err)
}
func TestFGetFinancialRecords(t *testing.T) {
t.Parallel()
sharedtestvalues.SkipTestIfCredentialsUnset(t, e)
_, err := e.FGetFinancialRecords(t.Context(),
"BTC", "closeLong", 2, 0, 0)
require.NoError(t, err)
}
func TestFGetSettlementRecords(t *testing.T) {
t.Parallel()
sharedtestvalues.SkipTestIfCredentialsUnset(t, e)
_, err := e.FGetSettlementRecords(t.Context(),
currency.BTC, 0, 0, time.Now().Add(-48*time.Hour), time.Now())
require.NoError(t, err)
}
func TestFContractTradingFee(t *testing.T) {
t.Parallel()
sharedtestvalues.SkipTestIfCredentialsUnset(t, e)
_, err := e.FContractTradingFee(t.Context(), currency.EMPTYCODE)
require.NoError(t, err)
}
func TestFGetTransferLimits(t *testing.T) {
t.Parallel()
sharedtestvalues.SkipTestIfCredentialsUnset(t, e)
_, err := e.FGetTransferLimits(t.Context(), currency.EMPTYCODE)
require.NoError(t, err)
}
func TestFGetPositionLimits(t *testing.T) {
t.Parallel()
sharedtestvalues.SkipTestIfCredentialsUnset(t, e)
_, err := e.FGetPositionLimits(t.Context(), currency.EMPTYCODE)
require.NoError(t, err)
}
func TestFGetAssetsAndPositions(t *testing.T) {
t.Parallel()
sharedtestvalues.SkipTestIfCredentialsUnset(t, e)
_, err := e.FGetAssetsAndPositions(t.Context(), currency.HT)
require.NoError(t, err)
}
func TestFTransfer(t *testing.T) {
t.Parallel()
sharedtestvalues.SkipTestIfCredentialsUnset(t, e)
_, err := e.FTransfer(t.Context(), "154263566", "HT", "sub_to_master", 5)
require.NoError(t, err)
}
func TestFGetTransferRecords(t *testing.T) {
t.Parallel()
sharedtestvalues.SkipTestIfCredentialsUnset(t, e)
_, err := e.FGetTransferRecords(t.Context(), "HT", "master_to_sub", 90, 0, 0)
require.NoError(t, err)
}
func TestFGetAvailableLeverage(t *testing.T) {
t.Parallel()
sharedtestvalues.SkipTestIfCredentialsUnset(t, e)
_, err := e.FGetAvailableLeverage(t.Context(), currency.BTC)
require.NoError(t, err)
}
func TestFOrder(t *testing.T) {
t.Parallel()
sharedtestvalues.SkipTestIfCredentialsUnset(t, e, canManipulateRealOrders)
_, err := e.FOrder(t.Context(), currency.EMPTYPAIR, "BTC", "quarter", "123", "BUY", "open", "limit", 1, 1, 1)
require.NoError(t, err)
}
func TestFPlaceBatchOrder(t *testing.T) {
t.Parallel()
sharedtestvalues.SkipTestIfCredentialsUnset(t, e, canManipulateRealOrders)
var req []fBatchOrderData
order1 := fBatchOrderData{
Symbol: "btc",
ContractType: "quarter",
ClientOrderID: "",
Price: 5,
Volume: 1,
Direction: "buy",
Offset: "open",
LeverageRate: 1,
OrderPriceType: "limit",
}
order2 := fBatchOrderData{
Symbol: "xrp",
ContractType: "this_week",
ClientOrderID: "",
Price: 10000,
Volume: 1,
Direction: "sell",
Offset: "open",
LeverageRate: 1,
OrderPriceType: "limit",
}
req = append(req, order1, order2)
_, err := e.FPlaceBatchOrder(t.Context(), req)
require.NoError(t, err)
}
func TestFCancelOrder(t *testing.T) {
t.Parallel()
sharedtestvalues.SkipTestIfCredentialsUnset(t, e, canManipulateRealOrders)
_, err := e.FCancelOrder(t.Context(), currency.BTC, "123", "")
require.NoError(t, err)
}
func TestFCancelAllOrders(t *testing.T) {
t.Parallel()
sharedtestvalues.SkipTestIfCredentialsUnset(t, e, canManipulateRealOrders)
updatePairsOnce(t, e)
_, err := e.FCancelAllOrders(t.Context(), btcFutureDatedPair, "", "")
require.NoError(t, err)
}
func TestFFlashCloseOrder(t *testing.T) {
t.Parallel()
sharedtestvalues.SkipTestIfCredentialsUnset(t, e, canManipulateRealOrders)
_, err := e.FFlashCloseOrder(t.Context(),
currency.EMPTYPAIR, "BTC", "quarter", "BUY", "lightning", "", 1)
require.NoError(t, err)
}
func TestFGetOrderInfo(t *testing.T) {
t.Parallel()
sharedtestvalues.SkipTestIfCredentialsUnset(t, e)
_, err := e.FGetOrderInfo(t.Context(), "BTC", "", "123")
require.NoError(t, err)
}
func TestFOrderDetails(t *testing.T) {
t.Parallel()
sharedtestvalues.SkipTestIfCredentialsUnset(t, e)
_, err := e.FOrderDetails(t.Context(), "BTC", "123", "quotation", time.Now().Add(-1*time.Hour), 0, 0)
require.NoError(t, err)
}
func TestFGetOpenOrders(t *testing.T) {
t.Parallel()
sharedtestvalues.SkipTestIfCredentialsUnset(t, e)
_, err := e.FGetOpenOrders(t.Context(), currency.BTC, 1, 2)
require.NoError(t, err)
}
func TestFGetOrderHistory(t *testing.T) {
t.Parallel()
sharedtestvalues.SkipTestIfCredentialsUnset(t, e)
_, err := e.FGetOrderHistory(t.Context(),
currency.EMPTYPAIR, "BTC",
"all", "all", "limit",
[]order.Status{},
5, 0, 0)
require.NoError(t, err)
}
func TestFTradeHistory(t *testing.T) {
t.Parallel()
sharedtestvalues.SkipTestIfCredentialsUnset(t, e)
_, err := e.FTradeHistory(t.Context(), currency.EMPTYPAIR, "BTC", "all", 10, 0, 0)
require.NoError(t, err)
}
func TestFPlaceTriggerOrder(t *testing.T) {
t.Parallel()
sharedtestvalues.SkipTestIfCredentialsUnset(t, e, canManipulateRealOrders)
_, err := e.FPlaceTriggerOrder(t.Context(), currency.EMPTYPAIR, "EOS", "quarter", "greaterOrEqual", "limit", "buy", "close", 1.1, 1.05, 5, 2)
require.NoError(t, err)
}
func TestFCancelTriggerOrder(t *testing.T) {
t.Parallel()
sharedtestvalues.SkipTestIfCredentialsUnset(t, e, canManipulateRealOrders)
_, err := e.FCancelTriggerOrder(t.Context(), "ETH", "123")
require.NoError(t, err)
}
func TestFCancelAllTriggerOrders(t *testing.T) {
t.Parallel()
sharedtestvalues.SkipTestIfCredentialsUnset(t, e, canManipulateRealOrders)
_, err := e.FCancelAllTriggerOrders(t.Context(), currency.EMPTYPAIR, "BTC", "this_week")
require.NoError(t, err)
}
func TestFQueryTriggerOpenOrders(t *testing.T) {
t.Parallel()
sharedtestvalues.SkipTestIfCredentialsUnset(t, e, canManipulateRealOrders)
_, err := e.FQueryTriggerOpenOrders(t.Context(), currency.EMPTYPAIR, "BTC", 0, 0)
require.NoError(t, err)
}
func TestFQueryTriggerOrderHistory(t *testing.T) {
t.Parallel()
sharedtestvalues.SkipTestIfCredentialsUnset(t, e, canManipulateRealOrders)
_, err := e.FQueryTriggerOrderHistory(t.Context(), currency.EMPTYPAIR, "EOS", "all", "all", 10, 0, 0)
require.NoError(t, err)
}
func TestFetchTradablePairs(t *testing.T) {
t.Parallel()
_, err := e.FetchTradablePairs(t.Context(), asset.Futures)
require.NoError(t, err)
}
func TestUpdateTickerSpot(t *testing.T) {
t.Parallel()
_, err := e.UpdateTicker(t.Context(), currency.NewPairWithDelimiter("INV", "ALID", "-"), asset.Spot)
assert.ErrorContains(t, err, "invalid symbol")
_, err = e.UpdateTicker(t.Context(), currency.NewPairWithDelimiter("BTC", "USDT", "_"), asset.Spot)
require.NoError(t, err)
}
func TestUpdateTickerCMF(t *testing.T) {
t.Parallel()
_, err := e.UpdateTicker(t.Context(), currency.NewPairWithDelimiter("INV", "ALID", "_"), asset.CoinMarginedFutures)
assert.ErrorContains(t, err, "symbol data error")
_, err = e.UpdateTicker(t.Context(), currency.NewPairWithDelimiter("BTC", "USD", "_"), asset.CoinMarginedFutures)
require.NoError(t, err)
}
func TestUpdateTickerFutures(t *testing.T) {
t.Parallel()
_, err := e.UpdateTicker(t.Context(), btccwPair, asset.Futures)
require.NoError(t, err)
}
func TestUpdateOrderbookSpot(t *testing.T) {
t.Parallel()
_, err := e.UpdateOrderbook(t.Context(), btcusdtPair, asset.Spot)
require.NoError(t, err)
}
func TestUpdateOrderbookCMF(t *testing.T) {
t.Parallel()
_, err := e.UpdateOrderbook(t.Context(), btcusdPair, asset.CoinMarginedFutures)
require.NoError(t, err)
}
func TestUpdateOrderbookFuture(t *testing.T) {
t.Parallel()
_, err := e.UpdateOrderbook(t.Context(), btccwPair, asset.Futures)
require.NoError(t, err)
_, err = e.UpdateOrderbook(t.Context(), btcusdPair, asset.CoinMarginedFutures)
require.NoError(t, err)
}
func TestGetOrderHistory(t *testing.T) {
t.Parallel()
sharedtestvalues.SkipTestIfCredentialsUnset(t, e)
updatePairsOnce(t, e)
getOrdersRequest := order.MultiOrderRequest{
Type: order.AnyType,
Pairs: []currency.Pair{currency.NewBTCUSDT()},
AssetType: asset.Spot,
Side: order.AnySide,
}
_, err := e.GetOrderHistory(t.Context(), &getOrdersRequest)
require.NoError(t, err)
getOrdersRequest.Pairs = []currency.Pair{btcusdPair}
getOrdersRequest.AssetType = asset.CoinMarginedFutures
_, err = e.GetOrderHistory(t.Context(), &getOrdersRequest)
require.NoError(t, err)
getOrdersRequest.Pairs = []currency.Pair{btcFutureDatedPair}
getOrdersRequest.AssetType = asset.Futures
_, err = e.GetOrderHistory(t.Context(), &getOrdersRequest)
require.NoError(t, err)
}
func TestCancelAllOrders(t *testing.T) {
t.Parallel()
sharedtestvalues.SkipTestIfCredentialsUnset(t, e, canManipulateRealOrders)
_, err := e.CancelAllOrders(t.Context(), &order.Cancel{AssetType: asset.Futures})
require.NoError(t, err)
}
func TestQuerySwapIndexPriceInfo(t *testing.T) {
t.Parallel()
_, err := e.QuerySwapIndexPriceInfo(t.Context(), btcusdPair)
require.NoError(t, err)
}
func TestSwapOpenInterestInformation(t *testing.T) {
t.Parallel()
_, err := e.SwapOpenInterestInformation(t.Context(), btcusdPair)
require.NoError(t, err)
}
func TestGetSwapMarketDepth(t *testing.T) {
t.Parallel()
_, err := e.GetSwapMarketDepth(t.Context(), btcusdPair, "step0")
require.NoError(t, err)
}
func TestGetSwapKlineData(t *testing.T) {
t.Parallel()
r, err := e.GetSwapKlineData(t.Context(), btcusdPair, "5min", 5, time.Now().Add(-time.Hour), time.Now())
require.NoError(t, err)
assert.NotEmpty(t, r.Data, "GetSwapKlineData should return some data")
}
func TestGetSwapMarketOverview(t *testing.T) {
t.Parallel()
_, err := e.GetSwapMarketOverview(t.Context(), btcusdPair)
require.NoError(t, err)
}
func TestGetLastTrade(t *testing.T) {
t.Parallel()
_, err := e.GetLastTrade(t.Context(), btcusdPair)
require.NoError(t, err)
}
func TestGetBatchTrades(t *testing.T) {
t.Parallel()
_, err := e.GetBatchTrades(t.Context(), btcusdPair, 5)
require.NoError(t, err)
}
func TestGetTieredAjustmentFactorInfo(t *testing.T) {
t.Parallel()
_, err := e.GetTieredAjustmentFactorInfo(t.Context(), btcusdPair)
require.NoError(t, err)
}
func TestGetOpenInterestInfo(t *testing.T) {
t.Parallel()
updatePairsOnce(t, e)
_, err := e.GetOpenInterestInfo(t.Context(), btcusdPair, "5min", "cryptocurrency", 50)
require.NoError(t, err)
}
func TestGetTraderSentimentIndexAccount(t *testing.T) {
t.Parallel()
_, err := e.GetTraderSentimentIndexAccount(t.Context(), btcusdPair, "5min")
require.NoError(t, err)
}
func TestGetTraderSentimentIndexPosition(t *testing.T) {
t.Parallel()
_, err := e.GetTraderSentimentIndexPosition(t.Context(), btcusdPair, "5min")
require.NoError(t, err)
}
func TestGetLiquidationOrders(t *testing.T) {
t.Parallel()
_, err := e.GetLiquidationOrders(t.Context(), btcusdPair, "closed", time.Now().AddDate(0, 0, -2), time.Now(), "", 0)
assert.NoError(t, err, "GetLiquidationOrders should not error")
}
func TestGetHistoricalFundingRates(t *testing.T) {
t.Parallel()
_, err := e.GetHistoricalFundingRatesForPair(t.Context(), btcusdPair, 0, 0)
require.NoError(t, err)
}
func TestGetPremiumIndexKlineData(t *testing.T) {
t.Parallel()
_, err := e.GetPremiumIndexKlineData(t.Context(), btcusdPair, "5min", 15)
require.NoError(t, err)
}
func TestGetEstimatedFundingRates(t *testing.T) {
t.Parallel()
_, err := e.GetPremiumIndexKlineData(t.Context(), btcusdPair, "5min", 15)
require.NoError(t, err)
}
func TestGetBasisData(t *testing.T) {
t.Parallel()
_, err := e.GetBasisData(t.Context(), btcusdPair, "5min", "close", 5)
require.NoError(t, err)
}
func TestGetSystemStatusInfo(t *testing.T) {
t.Parallel()
_, err := e.GetSystemStatusInfo(t.Context(), btcusdPair)
require.NoError(t, err)
}
func TestGetSwapPriceLimits(t *testing.T) {
t.Parallel()
_, err := e.GetSwapPriceLimits(t.Context(), btcusdPair)
require.NoError(t, err)
}
func TestGetMarginRates(t *testing.T) {
t.Parallel()
sharedtestvalues.SkipTestIfCredentialsUnset(t, e)
_, err := e.GetMarginRates(t.Context(), btcusdtPair)
require.NoError(t, err)
}
func TestGetSwapAccountInfo(t *testing.T) {
t.Parallel()
sharedtestvalues.SkipTestIfCredentialsUnset(t, e)
_, err := e.GetSwapAccountInfo(t.Context(), ethusdPair)
require.NoError(t, err)
}
func TestGetSwapPositionsInfo(t *testing.T) {
t.Parallel()
sharedtestvalues.SkipTestIfCredentialsUnset(t, e)
_, err := e.GetSwapPositionsInfo(t.Context(), ethusdPair)
require.NoError(t, err)
}
func TestGetSwapAssetsAndPositions(t *testing.T) {
t.Parallel()
sharedtestvalues.SkipTestIfCredentialsUnset(t, e)
_, err := e.GetSwapAssetsAndPositions(t.Context(), ethusdPair)
require.NoError(t, err)
}
func TestGetSwapAllSubAccAssets(t *testing.T) {
t.Parallel()
sharedtestvalues.SkipTestIfCredentialsUnset(t, e)
_, err := e.GetSwapAllSubAccAssets(t.Context(), ethusdPair)
require.NoError(t, err)
}
func TestGetSubAccPositionInfo(t *testing.T) {
t.Parallel()
sharedtestvalues.SkipTestIfCredentialsUnset(t, e)
_, err := e.GetSubAccPositionInfo(t.Context(), ethusdPair, 0)
require.NoError(t, err)
}
func TestGetAccountFinancialRecords(t *testing.T) {
t.Parallel()
sharedtestvalues.SkipTestIfCredentialsUnset(t, e)
_, err := e.GetAccountFinancialRecords(t.Context(), ethusdPair, "3,4", 15, 0, 0)
require.NoError(t, err)
}
func TestGetSwapSettlementRecords(t *testing.T) {
t.Parallel()
sharedtestvalues.SkipTestIfCredentialsUnset(t, e)
r, err := e.GetSwapSettlementRecords(t.Context(), ethusdPair, time.Now().AddDate(0, -1, 0), time.Now(), 0, 0)
require.NoError(t, err)
assert.NotEmpty(t, r.Data, "GetSwapSettlementRecords should return some data")
}
func TestGetAvailableLeverage(t *testing.T) {
t.Parallel()
sharedtestvalues.SkipTestIfCredentialsUnset(t, e)
_, err := e.GetAvailableLeverage(t.Context(), ethusdPair)
require.NoError(t, err)
}
func TestGetSwapOrderLimitInfo(t *testing.T) {
t.Parallel()
sharedtestvalues.SkipTestIfCredentialsUnset(t, e)
_, err := e.GetSwapOrderLimitInfo(t.Context(), ethusdPair, "limit")
require.NoError(t, err)
}
func TestGetSwapTradingFeeInfo(t *testing.T) {
t.Parallel()
sharedtestvalues.SkipTestIfCredentialsUnset(t, e)
_, err := e.GetSwapTradingFeeInfo(t.Context(), ethusdPair)
require.NoError(t, err)
}
func TestGetSwapTransferLimitInfo(t *testing.T) {
t.Parallel()
sharedtestvalues.SkipTestIfCredentialsUnset(t, e)
_, err := e.GetSwapTransferLimitInfo(t.Context(), ethusdPair)
require.NoError(t, err)
}
func TestGetSwapPositionLimitInfo(t *testing.T) {
t.Parallel()
sharedtestvalues.SkipTestIfCredentialsUnset(t, e)
_, err := e.GetSwapPositionLimitInfo(t.Context(), ethusdPair)
require.NoError(t, err)
}
func TestAccountTransferData(t *testing.T) {
t.Parallel()
sharedtestvalues.SkipTestIfCredentialsUnset(t, e)
_, err := e.AccountTransferData(t.Context(), ethusdPair, "123", "master_to_sub", 15)
require.NoError(t, err)
}
func TestAccountTransferRecords(t *testing.T) {
t.Parallel()
sharedtestvalues.SkipTestIfCredentialsUnset(t, e)
_, err := e.AccountTransferRecords(t.Context(), ethusdPair, "master_to_sub", 12, 0, 0)
require.NoError(t, err)
}
func TestPlaceSwapOrders(t *testing.T) {
t.Parallel()
sharedtestvalues.SkipTestIfCredentialsUnset(t, e, canManipulateRealOrders)
_, err := e.PlaceSwapOrders(t.Context(), ethusdPair, "", "buy", "open", "limit", 0.01, 1, 1)
require.NoError(t, err)
}
func TestPlaceSwapBatchOrders(t *testing.T) {
t.Parallel()
sharedtestvalues.SkipTestIfCredentialsUnset(t, e, canManipulateRealOrders)
var req BatchOrderRequestType
order1 := batchOrderData{
ContractCode: "ETH-USD",
ClientOrderID: "",
Price: 5,
Volume: 1,
Direction: "buy",
Offset: "open",
LeverageRate: 1,
OrderPriceType: "limit",
}
order2 := batchOrderData{
ContractCode: "BTC-USD",
ClientOrderID: "",
Price: 2.5,
Volume: 1,
Direction: "buy",
Offset: "open",
LeverageRate: 1,
OrderPriceType: "limit",
}
req.Data = append(req.Data, order1, order2)
_, err := e.PlaceSwapBatchOrders(t.Context(), req)
require.NoError(t, err)
}
func TestCancelSwapOrder(t *testing.T) {
t.Parallel()
sharedtestvalues.SkipTestIfCredentialsUnset(t, e, canManipulateRealOrders)
_, err := e.CancelSwapOrder(t.Context(), "test123", "", ethusdPair)
require.NoError(t, err)
}
func TestCancelAllSwapOrders(t *testing.T) {
t.Parallel()
sharedtestvalues.SkipTestIfCredentialsUnset(t, e, canManipulateRealOrders)
_, err := e.CancelAllSwapOrders(t.Context(), ethusdPair)
require.NoError(t, err)
}
func TestPlaceLightningCloseOrder(t *testing.T) {
t.Parallel()
sharedtestvalues.SkipTestIfCredentialsUnset(t, e, canManipulateRealOrders)
_, err := e.PlaceLightningCloseOrder(t.Context(), ethusdPair, "buy", "lightning", 5, 1)
require.NoError(t, err)
}
func TestGetSwapOrderInfo(t *testing.T) {
t.Parallel()
sharedtestvalues.SkipTestIfCredentialsUnset(t, e)
_, err := e.GetSwapOrderInfo(t.Context(), ethusdPair, "123", "")
require.NoError(t, err)
}
func TestGetSwapOrderDetails(t *testing.T) {
t.Parallel()
sharedtestvalues.SkipTestIfCredentialsUnset(t, e)
_, err := e.GetSwapOrderDetails(t.Context(), ethusdPair, "123", "10", "cancelledOrder", 0, 0)
require.NoError(t, err)
}
func TestGetSwapOpenOrders(t *testing.T) {
t.Parallel()
sharedtestvalues.SkipTestIfCredentialsUnset(t, e)
_, err := e.GetSwapOpenOrders(t.Context(), ethusdPair, 0, 0)
require.NoError(t, err)
}
func TestGetSwapOrderHistory(t *testing.T) {
t.Parallel()
sharedtestvalues.SkipTestIfCredentialsUnset(t, e)
_, err := e.GetSwapOrderHistory(t.Context(), ethusdPair, "all", "all", []order.Status{order.PartiallyCancelled, order.Active}, 25, 0, 0)
require.NoError(t, err)
}
func TestGetSwapTradeHistory(t *testing.T) {
t.Parallel()
sharedtestvalues.SkipTestIfCredentialsUnset(t, e)
_, err := e.GetSwapTradeHistory(t.Context(), ethusdPair, "liquidateShort", 10, 0, 0)
require.NoError(t, err)
}
func TestPlaceSwapTriggerOrder(t *testing.T) {
t.Parallel()
sharedtestvalues.SkipTestIfCredentialsUnset(t, e, canManipulateRealOrders)
_, err := e.PlaceSwapTriggerOrder(t.Context(), ethusdPair, "greaterOrEqual", "buy", "open", "optimal_5", 5, 3, 1, 1)
require.NoError(t, err)
}
func TestCancelSwapTriggerOrder(t *testing.T) {
t.Parallel()
sharedtestvalues.SkipTestIfCredentialsUnset(t, e, canManipulateRealOrders)
_, err := e.CancelSwapTriggerOrder(t.Context(), ethusdPair, "test123")
require.NoError(t, err)
}
func TestCancelAllSwapTriggerOrders(t *testing.T) {
t.Parallel()
sharedtestvalues.SkipTestIfCredentialsUnset(t, e, canManipulateRealOrders)
_, err := e.CancelAllSwapTriggerOrders(t.Context(), ethusdPair)
require.NoError(t, err)
}
func TestGetSwapTriggerOrderHistory(t *testing.T) {
t.Parallel()
sharedtestvalues.SkipTestIfCredentialsUnset(t, e)
_, err := e.GetSwapTriggerOrderHistory(t.Context(), ethusdPair, "open", "all", 15, 0, 0)
require.NoError(t, err)
}
func TestGetSwapMarkets(t *testing.T) {
t.Parallel()
_, err := e.GetSwapMarkets(t.Context(), currency.EMPTYPAIR)
require.NoError(t, err)
}
func TestGetSpotKline(t *testing.T) {
t.Parallel()
_, err := e.GetSpotKline(t.Context(), KlinesRequestParams{Symbol: btcusdtPair, Period: "1min"})
require.NoError(t, err)
}
func TestGetHistoricCandles(t *testing.T) {
t.Parallel()
e := new(Exchange) //nolint:govet // Intentional shadow
require.NoError(t, testexch.Setup(e), "Setup Instance must not error")
updatePairsOnce(t, e)
endTime := time.Now().Add(-time.Hour).Truncate(time.Hour)
_, err := e.GetHistoricCandles(t.Context(), btcusdtPair, asset.Spot, kline.OneMin, endTime.Add(-time.Hour), endTime)
require.NoError(t, err)
_, err = e.GetHistoricCandles(t.Context(), btcusdtPair, asset.Spot, kline.OneDay, endTime.AddDate(0, 0, -7), endTime)
require.NoError(t, err)
_, err = e.GetHistoricCandles(t.Context(), btcFutureDatedPair, asset.Futures, kline.OneDay, endTime.AddDate(0, 0, -7), endTime)
require.NoError(t, err)
_, err = e.GetHistoricCandles(t.Context(), btcusdPair, asset.CoinMarginedFutures, kline.OneDay, endTime.AddDate(0, 0, -7), endTime)
require.NoError(t, err)
}
func TestGetHistoricCandlesExtended(t *testing.T) {
t.Parallel()
e := new(Exchange) //nolint:govet // Intentional shadow
require.NoError(t, testexch.Setup(e), "Setup Instance must not error")
updatePairsOnce(t, e)
endTime := time.Now().Add(-time.Hour).Truncate(time.Hour)
_, err := e.GetHistoricCandlesExtended(t.Context(), btcusdtPair, asset.Spot, kline.OneMin, endTime.Add(-time.Hour), endTime)
require.ErrorIs(t, err, common.ErrFunctionNotSupported)
_, err = e.GetHistoricCandlesExtended(t.Context(), btcFutureDatedPair, asset.Futures, kline.OneDay, endTime.AddDate(0, 0, -7), endTime)
require.NoError(t, err)
// demonstrate that adjusting time doesn't wreck non-day intervals
_, err = e.GetHistoricCandlesExtended(t.Context(), btcFutureDatedPair, asset.Futures, kline.OneHour, endTime.AddDate(0, 0, -1), endTime)
require.NoError(t, err)
_, err = e.GetHistoricCandlesExtended(t.Context(), btcusdPair, asset.CoinMarginedFutures, kline.OneDay, endTime.AddDate(0, 0, -7), time.Now())
require.NoError(t, err)
_, err = e.GetHistoricCandlesExtended(t.Context(), btcusdPair, asset.CoinMarginedFutures, kline.OneHour, endTime.AddDate(0, 0, -1), time.Now())
require.NoError(t, err)
}
func TestGetMarketDetailMerged(t *testing.T) {
t.Parallel()
_, err := e.GetMarketDetailMerged(t.Context(), btcusdtPair)
require.NoError(t, err)
}
func TestGetDepth(t *testing.T) {
t.Parallel()
_, err := e.GetDepth(t.Context(),
&OrderBookDataRequestParams{
Symbol: btcusdtPair,
Type: OrderBookDataRequestParamsTypeStep1,
})
require.NoError(t, err)
}
func TestGetTrades(t *testing.T) {
t.Parallel()
_, err := e.GetTrades(t.Context(), btcusdtPair)
require.NoError(t, err)
}
func TestGetLatestSpotPrice(t *testing.T) {
t.Parallel()
_, err := e.GetLatestSpotPrice(t.Context(), btcusdtPair)
require.NoError(t, err)
}
func TestGetTradeHistory(t *testing.T) {
t.Parallel()
_, err := e.GetTradeHistory(t.Context(), btcusdtPair, 50)
require.NoError(t, err)
}
func TestGetMarketDetail(t *testing.T) {
t.Parallel()
_, err := e.GetMarketDetail(t.Context(), btcusdtPair)
require.NoError(t, err)
}
func TestGetSymbols(t *testing.T) {
t.Parallel()
_, err := e.GetSymbols(t.Context())
require.NoError(t, err)
}
func TestGetCurrencies(t *testing.T) {
t.Parallel()
_, err := e.GetCurrencies(t.Context())
require.NoError(t, err)
}
func TestGet24HrMarketSummary(t *testing.T) {
t.Parallel()
_, err := e.Get24HrMarketSummary(t.Context(), btcusdtPair)
require.NoError(t, err)
}
func TestGetTicker(t *testing.T) {
t.Parallel()
_, err := e.GetTickers(t.Context())
require.NoError(t, err)
}
func TestGetTimestamp(t *testing.T) {
t.Parallel()
st, err := e.GetCurrentServerTime(t.Context())
require.NoError(t, err)
assert.NotEmpty(t, st, "GetCurrentServerTime should return a time")
}
func TestWrapperGetServerTime(t *testing.T) {
t.Parallel()
st, err := e.GetServerTime(t.Context(), asset.Spot)
require.NoError(t, err)
assert.NotEmpty(t, st, "GetServerTime should return a time")
}
func TestGetAccounts(t *testing.T) {
t.Parallel()
sharedtestvalues.SkipTestIfCredentialsUnset(t, e, canManipulateRealOrders)
_, err := e.GetAccounts(t.Context())
require.NoError(t, err)
}
func TestGetAccountBalance(t *testing.T) {
t.Parallel()
sharedtestvalues.SkipTestIfCredentialsUnset(t, e, canManipulateRealOrders)
result, err := e.GetAccounts(t.Context())
require.NoError(t, err, "GetAccounts must not error")
userID := strconv.FormatInt(result[0].ID, 10)
_, err = e.GetAccountBalance(t.Context(), userID)
require.NoError(t, err, "GetAccountBalance must not error")
}
func TestGetAggregatedBalance(t *testing.T) {
t.Parallel()
sharedtestvalues.SkipTestIfCredentialsUnset(t, e)
_, err := e.GetAggregatedBalance(t.Context())
require.NoError(t, err)
}
func TestSpotNewOrder(t *testing.T) {
t.Parallel()
sharedtestvalues.SkipTestIfCredentialsUnset(t, e, canManipulateRealOrders)
arg := SpotNewOrderRequestParams{
Symbol: btcusdtPair,
AccountID: 1997024,
Amount: 0.01,
Price: 10.1,
Type: SpotNewOrderRequestTypeBuyLimit,
}
_, err := e.SpotNewOrder(t.Context(), &arg)
require.NoError(t, err)
}
func TestCancelExistingOrder(t *testing.T) {
t.Parallel()
sharedtestvalues.SkipTestIfCredentialsUnset(t, e, canManipulateRealOrders)
_, err := e.CancelExistingOrder(t.Context(), 1337)
assert.Error(t, err)
}
func TestGetOrder(t *testing.T) {
t.Parallel()
sharedtestvalues.SkipTestIfCredentialsUnset(t, e, canManipulateRealOrders)
_, err := e.GetOrder(t.Context(), 1337)
require.NoError(t, err)
}
func TestGetMarginLoanOrders(t *testing.T) {
t.Parallel()
sharedtestvalues.SkipTestIfCredentialsUnset(t, e)
_, err := e.GetMarginLoanOrders(t.Context(), btcusdtPair, "", "", "", "", "", "", "")
require.NoError(t, err)
}
func TestGetMarginAccountBalance(t *testing.T) {
t.Parallel()
sharedtestvalues.SkipTestIfCredentialsUnset(t, e)
_, err := e.GetMarginAccountBalance(t.Context(), btcusdtPair)
require.NoError(t, err)
}
func TestCancelWithdraw(t *testing.T) {
t.Parallel()
sharedtestvalues.SkipTestIfCredentialsUnset(t, e, canManipulateRealOrders)
_, err := e.CancelWithdraw(t.Context(), 1337)
require.Error(t, err)
}
func setFeeBuilder() *exchange.FeeBuilder {
return &exchange.FeeBuilder{
Amount: 1,
FeeType: exchange.CryptocurrencyTradeFee,
Pair: currency.NewPairWithDelimiter(currency.BTC.String(),
currency.LTC.String(),
"_"),
PurchasePrice: 1,
FiatCurrency: currency.USD,
BankTransactionType: exchange.WireTransfer,
}
}
func TestGetFeeByTypeOfflineTradeFee(t *testing.T) {
feeBuilder := setFeeBuilder()
_, err := e.GetFeeByType(t.Context(), feeBuilder)
require.NoError(t, err)
if !sharedtestvalues.AreAPICredentialsSet(e) {
assert.Equal(t, exchange.OfflineTradeFee, feeBuilder.FeeType)
} else {
assert.Equal(t, exchange.CryptocurrencyTradeFee, feeBuilder.FeeType)
}
}
func TestGetFee(t *testing.T) {
t.Parallel()
feeBuilder := setFeeBuilder()
// CryptocurrencyTradeFee Basic
_, err := e.GetFee(feeBuilder)
require.NoError(t, err)
// CryptocurrencyTradeFee High quantity
feeBuilder = setFeeBuilder()
feeBuilder.Amount = 1000
feeBuilder.PurchasePrice = 1000
_, err = e.GetFee(feeBuilder)
require.NoError(t, err)
// CryptocurrencyTradeFee IsMaker
feeBuilder = setFeeBuilder()
feeBuilder.IsMaker = true
_, err = e.GetFee(feeBuilder)
require.NoError(t, err)
// CryptocurrencyTradeFee Negative purchase price
feeBuilder = setFeeBuilder()
feeBuilder.PurchasePrice = -1000
_, err = e.GetFee(feeBuilder)
require.NoError(t, err)
// CryptocurrencyWithdrawalFee Basic
feeBuilder = setFeeBuilder()
feeBuilder.FeeType = exchange.CryptocurrencyWithdrawalFee
_, err = e.GetFee(feeBuilder)
require.NoError(t, err)
// CryptocurrencyWithdrawalFee Invalid currency
feeBuilder = setFeeBuilder()
feeBuilder.Pair.Base = currency.NewCode("hello")
feeBuilder.FeeType = exchange.CryptocurrencyWithdrawalFee
_, err = e.GetFee(feeBuilder)
require.NoError(t, err)
// CryptocurrencyDepositFee Basic
feeBuilder = setFeeBuilder()
feeBuilder.FeeType = exchange.CryptocurrencyDepositFee
_, err = e.GetFee(feeBuilder)
require.NoError(t, err)
// InternationalBankDepositFee Basic
feeBuilder = setFeeBuilder()
feeBuilder.FeeType = exchange.InternationalBankDepositFee
_, err = e.GetFee(feeBuilder)
require.NoError(t, err)
// InternationalBankWithdrawalFee Basic
feeBuilder = setFeeBuilder()
feeBuilder.FeeType = exchange.InternationalBankWithdrawalFee
feeBuilder.FiatCurrency = currency.USD
_, err = e.GetFee(feeBuilder)
require.NoError(t, err)
}
func TestFormatWithdrawPermissions(t *testing.T) {
t.Parallel()
expectedResult := exchange.AutoWithdrawCryptoWithSetupText + " & " + exchange.NoFiatWithdrawalsText
withdrawPermissions := e.FormatWithdrawPermissions()
assert.Equal(t, expectedResult, withdrawPermissions)
}
func TestGetActiveOrders(t *testing.T) {
t.Parallel()
getOrdersRequest := order.MultiOrderRequest{
AssetType: asset.Spot,
Type: order.AnyType,
Pairs: []currency.Pair{currency.NewBTCUSDT()},
Side: order.AnySide,
}
_, err := e.GetActiveOrders(t.Context(), &getOrdersRequest)
if sharedtestvalues.AreAPICredentialsSet(e) {
require.NoError(t, err)
} else {
require.ErrorIs(t, err, exchange.ErrAuthenticationSupportNotEnabled)
}
}
// Any tests below this line have the ability to impact your orders on the exchange. Enable canManipulateRealOrders to run them
// ----------------------------------------------------------------------------------------------------------------------------
func TestSubmitOrder(t *testing.T) {
t.Parallel()
sharedtestvalues.SkipTestIfCredentialsUnset(t, e, canManipulateRealOrders)
accounts, err := e.GetAccounts(t.Context())
require.NoError(t, err, "GetAccounts must not error")
orderSubmission := &order.Submit{
Exchange: e.Name,
Pair: currency.Pair{
Base: currency.BTC,
Quote: currency.USDT,
},
Side: order.Buy,
Type: order.Limit,
Price: 5,
Amount: 1,
ClientID: strconv.FormatInt(accounts[0].ID, 10),
AssetType: asset.Spot,
}
response, err := e.SubmitOrder(t.Context(), orderSubmission)
require.NoError(t, err)
assert.Equal(t, order.New, response.Status, "response status should be correct")
}
func TestCancelExchangeOrder(t *testing.T) {
t.Parallel()
sharedtestvalues.SkipTestIfCredentialsUnset(t, e, canManipulateRealOrders)
orderCancellation := &order.Cancel{
OrderID: "1",
AccountID: "1",
Pair: btcusdtPair,
AssetType: asset.Spot,
}
err := e.CancelOrder(t.Context(), orderCancellation)
require.NoError(t, err)
}
func TestCancelAllExchangeOrders(t *testing.T) {
t.Parallel()
sharedtestvalues.SkipTestIfCredentialsUnset(t, e, canManipulateRealOrders)
currencyPair := currency.NewPair(currency.LTC, currency.BTC)
orderCancellation := order.Cancel{
OrderID: "1",
AccountID: "1",
Pair: currencyPair,
AssetType: asset.Spot,
}
_, err := e.CancelAllOrders(t.Context(), &orderCancellation)
require.NoError(t, err)
}
func TestUpdateAccountInfo(t *testing.T) {
t.Parallel()
sharedtestvalues.SkipTestIfCredentialsUnset(t, e, canManipulateRealOrders)
for _, a := range []asset.Item{asset.Spot, asset.CoinMarginedFutures, asset.Futures} {
_, err := e.UpdateAccountInfo(t.Context(), a)
assert.NoErrorf(t, err, "UpdateAccountInfo should not error for asset %s", a)
}
}
func TestModifyOrder(t *testing.T) {
t.Parallel()
sharedtestvalues.SkipTestIfCannotManipulateOrders(t, e, canManipulateRealOrders)
_, err := e.ModifyOrder(t.Context(), &order.Modify{AssetType: asset.Spot})
require.Error(t, err, "ModifyOrder must error without any order details")
}
func TestWithdraw(t *testing.T) {
t.Parallel()
sharedtestvalues.SkipTestIfCannotManipulateOrders(t, e, canManipulateRealOrders)
withdrawCryptoRequest := withdraw.Request{
Exchange: e.Name,
Amount: -1,
Currency: currency.BTC,
Description: "WITHDRAW IT ALL",
Crypto: withdraw.CryptoRequest{
Address: core.BitcoinDonationAddress,
},
}
_, err := e.WithdrawCryptocurrencyFunds(t.Context(), &withdrawCryptoRequest)
require.ErrorContains(t, err, withdraw.ErrStrAmountMustBeGreaterThanZero)
}
func TestWithdrawFiat(t *testing.T) {
t.Parallel()
_, err := e.WithdrawFiatFunds(t.Context(), &withdraw.Request{})
assert.ErrorIs(t, err, common.ErrFunctionNotSupported)
}
func TestWithdrawInternationalBank(t *testing.T) {
t.Parallel()
_, err := e.WithdrawFiatFundsToInternationalBank(t.Context(), &withdraw.Request{})
assert.ErrorIs(t, err, common.ErrFunctionNotSupported)
}
func TestQueryDepositAddress(t *testing.T) {
t.Parallel()
_, err := e.QueryDepositAddress(t.Context(), currency.USDT)
if sharedtestvalues.AreAPICredentialsSet(e) {
require.NoError(t, err)
} else {
require.ErrorIs(t, err, exchange.ErrAuthenticationSupportNotEnabled)
}
}
func TestGetDepositAddress(t *testing.T) {
t.Parallel()
_, err := e.GetDepositAddress(t.Context(), currency.USDT, "", "uSdTeRc20")
if sharedtestvalues.AreAPICredentialsSet(e) {
require.NoError(t, err)
} else {
require.ErrorIs(t, err, exchange.ErrAuthenticationSupportNotEnabled)
}
}
func TestQueryWithdrawQuota(t *testing.T) {
t.Parallel()
_, err := e.QueryWithdrawQuotas(t.Context(), currency.BTC.Lower().String())
if sharedtestvalues.AreAPICredentialsSet(e) {
require.NoError(t, err)
} else {
require.ErrorIs(t, err, exchange.ErrAuthenticationSupportNotEnabled)
}
}
func TestWSCandles(t *testing.T) {
t.Parallel()
e := new(Exchange) //nolint:govet // Intentional shadow
require.NoError(t, testexch.Setup(e), "Setup Instance must not error")
err := e.Websocket.AddSubscriptions(e.Websocket.Conn, &subscription.Subscription{Key: "market.btcusdt.kline.1min", Asset: asset.Spot, Pairs: currency.Pairs{btcusdtPair}, Channel: subscription.CandlesChannel})
require.NoError(t, err, "AddSubscriptions must not error")
testexch.FixtureToDataHandler(t, "testdata/wsCandles.json", e.wsHandleData)
close(e.Websocket.DataHandler)
require.Len(t, e.Websocket.DataHandler, 1, "Must see correct number of records")
cAny := <-e.Websocket.DataHandler
c, ok := cAny.(websocket.KlineData)
require.True(t, ok, "Must get the correct type from DataHandler")
exp := websocket.KlineData{
Timestamp: time.UnixMilli(1489474082831),
Pair: btcusdtPair,
AssetType: asset.Spot,
Exchange: e.Name,
OpenPrice: 7962.62,
ClosePrice: 8014.56,
HighPrice: 14962.77,
LowPrice: 5110.14,
Volume: 4.4,
Interval: "0s",
}
assert.Equal(t, exp, c)
}
func TestWSOrderbook(t *testing.T) {
t.Parallel()
e := new(Exchange) //nolint:govet // Intentional shadow
require.NoError(t, testexch.Setup(e), "Setup Instance must not error")
err := e.Websocket.AddSubscriptions(e.Websocket.Conn, &subscription.Subscription{Key: "market.btcusdt.depth.step0", Asset: asset.Spot, Pairs: currency.Pairs{btcusdtPair}, Channel: subscription.OrderbookChannel})
require.NoError(t, err, "AddSubscriptions must not error")
testexch.FixtureToDataHandler(t, "testdata/wsOrderbook.json", e.wsHandleData)
close(e.Websocket.DataHandler)
require.Len(t, e.Websocket.DataHandler, 1, "Must see correct number of records")
dAny := <-e.Websocket.DataHandler
d, ok := dAny.(*orderbook.Depth)
require.True(t, ok, "Must get the correct type from DataHandler")
require.NotNil(t, d)
l, err := d.GetAskLength()
require.NoError(t, err, "GetAskLength must not error")
assert.Equal(t, 2, l, "Ask length should be correct")
liq, _, err := d.TotalAskAmounts()
require.NoError(t, err, "TotalAskAmount must not error")
assert.Equal(t, 0.502591, liq, "Ask Liquidity should be correct")
l, err = d.GetBidLength()
require.NoError(t, err, "GetBidLength must not error")
assert.Equal(t, 2, l, "Bid length should be correct")
liq, _, err = d.TotalBidAmounts()
require.NoError(t, err, "TotalBidAmount must not error")
assert.Equal(t, 0.56281, liq, "Bid Liquidity should be correct")
}
// TestWSHandleAllTradesMsg ensures wsHandleAllTrades sends trade.Data to the ws.DataHandler
func TestWSHandleAllTradesMsg(t *testing.T) {
t.Parallel()
e := new(Exchange) //nolint:govet // Intentional shadow
require.NoError(t, testexch.Setup(e), "Setup Instance must not error")
err := e.Websocket.AddSubscriptions(e.Websocket.Conn, &subscription.Subscription{Key: "market.btcusdt.trade.detail", Asset: asset.Spot, Pairs: currency.Pairs{btcusdtPair}, Channel: subscription.AllTradesChannel})
require.NoError(t, err, "AddSubscriptions must not error")
e.SetSaveTradeDataStatus(true)
testexch.FixtureToDataHandler(t, "testdata/wsAllTrades.json", e.wsHandleData)
close(e.Websocket.DataHandler)
exp := []trade.Data{
{
Exchange: e.Name,
CurrencyPair: btcusdtPair,
Timestamp: time.UnixMilli(1630994963173).UTC(),
Price: 52648.62,
Amount: 0.006754,
Side: order.Buy,
TID: "102523573486",
AssetType: asset.Spot,
},
{
Exchange: e.Name,
CurrencyPair: btcusdtPair,
Timestamp: time.UnixMilli(1630994963184).UTC(),
Price: 52648.73,
Amount: 0.006755,
Side: order.Sell,
TID: "102523573487",
AssetType: asset.Spot,
},
}
require.Len(t, e.Websocket.DataHandler, 2, "Must see correct number of trades")
for resp := range e.Websocket.DataHandler {
switch v := resp.(type) {
case trade.Data:
i := 1 - len(e.Websocket.DataHandler)
require.Equalf(t, exp[i], v, "Trade [%d] must be correct", i)
case error:
t.Error(v)
default:
t.Errorf("Unexpected type in DataHandler: %T(%s)", v, v)
}
}
require.Empty(t, e.Websocket.DataHandler, "Must not see any errors going to datahandler")
}
func TestWSTicker(t *testing.T) {
t.Parallel()
e := new(Exchange) //nolint:govet // Intentional shadow
require.NoError(t, testexch.Setup(e), "Setup Instance must not error")
err := e.Websocket.AddSubscriptions(e.Websocket.Conn, &subscription.Subscription{Key: "market.btcusdt.detail", Asset: asset.Spot, Pairs: currency.Pairs{btcusdtPair}, Channel: subscription.TickerChannel})
require.NoError(t, err, "AddSubscriptions must not error")
testexch.FixtureToDataHandler(t, "testdata/wsTicker.json", e.wsHandleData)
close(e.Websocket.DataHandler)
require.Len(t, e.Websocket.DataHandler, 1, "Must see correct number of records")
tickAny := <-e.Websocket.DataHandler
tick, ok := tickAny.(*ticker.Price)
require.True(t, ok, "Must get the correct type from DataHandler")
require.NotNil(t, tick)
exp := &ticker.Price{
High: 52924.14,
Low: 51000,
Bid: 0,
Volume: 13991.028076056185,
QuoteVolume: 7.27676440200527e+08,
Open: 51823.62,
Close: 52379.99,
Pair: btcusdtPair,
ExchangeName: e.Name,
AssetType: asset.Spot,
LastUpdated: time.UnixMilli(1630998026649),
}
assert.Equal(t, exp, tick)
}
func TestWSAccountUpdate(t *testing.T) {
t.Parallel()
e := new(Exchange) //nolint:govet // Intentional shadow
require.NoError(t, testexch.Setup(e), "Setup Instance must not error")
err := e.Websocket.AddSubscriptions(e.Websocket.Conn, &subscription.Subscription{Key: "accounts.update#2", Asset: asset.Spot, Pairs: currency.Pairs{btcusdtPair}, Channel: subscription.MyAccountChannel})
require.NoError(t, err, "AddSubscriptions must not error")
e.SetSaveTradeDataStatus(true)
testexch.FixtureToDataHandler(t, "testdata/wsMyAccount.json", e.wsHandleData)
close(e.Websocket.DataHandler)
require.Len(t, e.Websocket.DataHandler, 3, "Must see correct number of records")
exp := []WsAccountUpdate{
{Currency: "btc", AccountID: 123456, Balance: 23.111, ChangeType: "transfer", AccountType: "trade", ChangeTime: types.Time(time.UnixMilli(1568601800000)), SeqNum: 1},
{Currency: "btc", AccountID: 33385, Available: 2028.69, ChangeType: "order.match", AccountType: "trade", ChangeTime: types.Time(time.UnixMilli(1574393385167)), SeqNum: 2},
{Currency: "usdt", AccountID: 14884859, Available: 20.29388158, Balance: 20.29388158, AccountType: "trade", SeqNum: 3},
}
for _, ex := range exp {
uAny := <-e.Websocket.DataHandler
u, ok := uAny.(WsAccountUpdate)
require.True(t, ok, "Must get the correct type from DataHandler")
require.NotNil(t, u)
assert.Equal(t, ex, u)
}
}
func TestWSOrderUpdate(t *testing.T) {
t.Parallel()
e := new(Exchange) //nolint:govet // Intentional shadow
require.NoError(t, testexch.Setup(e), "Setup Instance must not error")
err := e.Websocket.AddSubscriptions(e.Websocket.Conn, &subscription.Subscription{Key: "orders#*", Asset: asset.Spot, Pairs: currency.Pairs{btcusdtPair}, Channel: subscription.MyOrdersChannel})
require.NoError(t, err, "AddSubscriptions must not error")
e.SetSaveTradeDataStatus(true)
errs := testexch.FixtureToDataHandlerWithErrors(t, "testdata/wsMyOrders.json", e.wsHandleData)
close(e.Websocket.DataHandler)
require.Equal(t, 1, len(errs), "Must receive the correct number of errors back")
require.ErrorContains(t, errs[0].Err, "error with order \"test1\": invalid.client.order.id (NT) (2002)")
require.Len(t, e.Websocket.DataHandler, 4, "Must see correct number of records")
exp := []*order.Detail{
{
Exchange: e.Name,
Pair: btcusdtPair,
Side: order.Buy,
Status: order.Rejected,
ClientOrderID: "test1",
AssetType: asset.Spot,
LastUpdated: time.UnixMicro(1583853365586000),
},
{
Exchange: e.Name,
Pair: btcusdtPair,
Side: order.Buy,
Status: order.Cancelled,
ClientOrderID: "test2",
AssetType: asset.Spot,
LastUpdated: time.UnixMicro(1583853365586000),
},
{
Exchange: e.Name,
Pair: btcusdtPair,
Side: order.Sell,
Status: order.New,
ClientOrderID: "test3",
AssetType: asset.Spot,
Price: 77,
Amount: 2,
Type: order.Limit,
OrderID: "27163533",
LastUpdated: time.UnixMicro(1583853365586000),
},
{
Exchange: e.Name,
Pair: btcusdtPair,
Side: order.Buy,
Status: order.New,
AssetType: asset.Spot,
Price: 70000,
Amount: 0.000157,
Type: order.Limit,
OrderID: "1199329381585359",
LastUpdated: time.UnixMicro(1731039387696000),
},
}
for _, ex := range exp {
m := <-e.Websocket.DataHandler
require.IsType(t, &order.Detail{}, m, "Must get the correct type from DataHandler")
d, _ := m.(*order.Detail)
require.NotNil(t, d)
assert.Equal(t, ex, d, "Order Detail should match")
}
}
func TestWSMyTrades(t *testing.T) {
t.Parallel()
e := new(Exchange) //nolint:govet // Intentional shadow
require.NoError(t, testexch.Setup(e), "Setup Instance must not error")
err := e.Websocket.AddSubscriptions(e.Websocket.Conn, &subscription.Subscription{Key: "trade.clearing#btcusdt#1", Asset: asset.Spot, Pairs: currency.Pairs{btcusdtPair}, Channel: subscription.MyTradesChannel})
require.NoError(t, err, "AddSubscriptions must not error")
e.SetSaveTradeDataStatus(true)
testexch.FixtureToDataHandler(t, "testdata/wsMyTrades.json", e.wsHandleData)
close(e.Websocket.DataHandler)
require.Len(t, e.Websocket.DataHandler, 1, "Must see correct number of records")
m := <-e.Websocket.DataHandler
exp := &order.Detail{
Exchange: e.Name,
Pair: btcusdtPair,
Side: order.Buy,
Status: order.PartiallyFilled,
ClientOrderID: "a001",
OrderID: "99998888",
AssetType: asset.Spot,
Date: time.UnixMicro(1583853365586000),
LastUpdated: time.UnixMicro(1583853365996000),
Price: 10000,
Amount: 1,
Trades: []order.TradeHistory{
{
Price: 9999.99,
Amount: 0.96,
Fee: 19.88,
Exchange: e.Name,
TID: "919219323232",
Side: order.Buy,
IsMaker: false,
Timestamp: time.UnixMicro(1583853365996000),
},
},
}
require.IsType(t, &order.Detail{}, m, "Must get the correct type from DataHandler")
d, _ := m.(*order.Detail)
require.NotNil(t, d)
assert.Equal(t, exp, d, "Order Detail should match")
}
func TestStringToOrderStatus(t *testing.T) {
t.Parallel()
type TestCases struct {
Case string
Result order.Status
}
testCases := []TestCases{
{Case: "submitted", Result: order.New},
{Case: "canceled", Result: order.Cancelled},
{Case: "partial-filled", Result: order.PartiallyFilled},
{Case: "partial-canceled", Result: order.PartiallyCancelled},
{Case: "LOL", Result: order.UnknownStatus},
}
for i := range testCases {
result, _ := stringToOrderStatus(testCases[i].Case)
if result != testCases[i].Result {
t.Errorf("Expected: %v, received: %v", testCases[i].Result, result)
}
}
}
func TestStringToOrderSide(t *testing.T) {
t.Parallel()
type TestCases struct {
Case string
Result order.Side
}
testCases := []TestCases{
{Case: "buy-limit", Result: order.Buy},
{Case: "sell-limit", Result: order.Sell},
{Case: "woah-nelly", Result: order.UnknownSide},
}
for i := range testCases {
result, _ := stringToOrderSide(testCases[i].Case)
if result != testCases[i].Result {
t.Errorf("Expected: %v, received: %v", testCases[i].Result, result)
}
}
}
func TestStringToOrderType(t *testing.T) {
t.Parallel()
type TestCases struct {
Case string
Result order.Type
}
testCases := []TestCases{
{Case: "buy-limit", Result: order.Limit},
{Case: "sell-market", Result: order.Market},
{Case: "woah-nelly", Result: order.UnknownType},
}
for i := range testCases {
result, _ := stringToOrderType(testCases[i].Case)
if result != testCases[i].Result {
t.Errorf("Expected: %v, received: %v", testCases[i].Result, result)
}
}
}
func TestFormatExchangeKlineInterval(t *testing.T) {
t.Parallel()
for _, tt := range []struct {
interval kline.Interval
output string
}{
{kline.OneMin, "1min"},
{kline.FourHour, "4hour"},
{kline.OneDay, "1day"},
{kline.OneWeek, "1week"},
{kline.OneMonth, "1mon"},
{kline.OneYear, "1year"},
{kline.TwoWeek, ""},
} {
assert.Equalf(t, tt.output, e.FormatExchangeKlineInterval(tt.interval), "FormatExchangeKlineInterval should return correctly for %s", tt.output)
}
}
func TestGetRecentTrades(t *testing.T) {
t.Parallel()
_, err := e.GetRecentTrades(t.Context(), btcusdtPair, asset.Spot)
require.NoError(t, err)
_, err = e.GetRecentTrades(t.Context(), btccwPair, asset.Futures)
require.NoError(t, err)
_, err = e.GetRecentTrades(t.Context(), btcusdPair, asset.CoinMarginedFutures)
require.NoError(t, err)
}
func TestGetHistoricTrades(t *testing.T) {
t.Parallel()
_, err := e.GetHistoricTrades(t.Context(), btcusdtPair, asset.Spot, time.Now().Add(-time.Minute*15), time.Now())
require.ErrorIs(t, err, common.ErrFunctionNotSupported)
}
func TestGetAvailableTransferChains(t *testing.T) {
t.Parallel()
c, err := e.GetAvailableTransferChains(t.Context(), currency.USDT)
require.NoError(t, err)
require.Greater(t, len(c), 2, "Must get more than 2 chains")
}
func TestFormatFuturesPair(t *testing.T) {
t.Parallel()
updatePairsOnce(t, e)
r, err := e.formatFuturesPair(btccwPair, false)
require.NoError(t, err)
assert.Equal(t, "BTC_CW", r)
// pair in the format of BTC210827 but make it lower case to test correct formatting
r, err = e.formatFuturesPair(btcFutureDatedPair.Lower(), false)
require.NoError(t, err)
assert.Len(t, r, 9, "Should be an 9 character string")
assert.Equal(t, "BTC2", r[0:4], "Should start with btc and a date this millennium")
r, err = e.formatFuturesPair(btccwPair, true)
require.NoError(t, err)
assert.Len(t, r, 9, "Should be an 9 character string")
assert.Equal(t, "BTC2", r[0:4], "Should start with btc and a date this millennium")
r, err = e.formatFuturesPair(currency.NewBTCUSDT(), false)
require.NoError(t, err)
assert.Equal(t, "BTC-USDT", r)
}
func TestSearchForExistedWithdrawsAndDeposits(t *testing.T) {
t.Parallel()
sharedtestvalues.SkipTestIfCredentialsUnset(t, e)
_, err := e.SearchForExistedWithdrawsAndDeposits(t.Context(), currency.BTC, "deposit", "", 0, 100)
require.NoError(t, err)
}
func TestCancelOrderBatch(t *testing.T) {
t.Parallel()
sharedtestvalues.SkipTestIfCredentialsUnset(t, e, canManipulateRealOrders)
_, err := e.CancelOrderBatch(t.Context(), []string{"1234"}, nil)
require.NoError(t, err)
}
func TestCancelBatchOrders(t *testing.T) {
t.Parallel()
sharedtestvalues.SkipTestIfCredentialsUnset(t, e, canManipulateRealOrders)
_, err := e.CancelBatchOrders(t.Context(), []order.Cancel{
{
OrderID: "1234",
AssetType: asset.Spot,
Pair: currency.NewBTCUSDT(),
},
})
require.NoError(t, err)
}
func TestGetWithdrawalsHistory(t *testing.T) {
t.Parallel()
sharedtestvalues.SkipTestIfCredentialsUnset(t, e)
_, err := e.GetWithdrawalsHistory(t.Context(), currency.BTC, asset.Spot)
require.NoError(t, err)
}
func TestGetFuturesContractDetails(t *testing.T) {
t.Parallel()
_, err := e.GetFuturesContractDetails(t.Context(), asset.Spot)
require.ErrorIs(t, err, futures.ErrNotFuturesAsset)
_, err = e.GetFuturesContractDetails(t.Context(), asset.USDTMarginedFutures)
require.ErrorIs(t, err, asset.ErrNotSupported)
_, err = e.GetFuturesContractDetails(t.Context(), asset.CoinMarginedFutures)
require.NoError(t, err)
_, err = e.GetFuturesContractDetails(t.Context(), asset.Futures)
require.NoError(t, err)
}
func TestGetLatestFundingRates(t *testing.T) {
t.Parallel()
e := new(Exchange) //nolint:govet // Intentional shadow
require.NoError(t, testexch.Setup(e), "Test Instance Setup must not fail")
updatePairsOnce(t, e)
_, err := e.GetLatestFundingRates(t.Context(), &fundingrate.LatestRateRequest{
Asset: asset.USDTMarginedFutures,
Pair: currency.NewBTCUSD(),
IncludePredictedRate: true,
})
require.ErrorIs(t, err, asset.ErrNotSupported)
_, err = e.GetLatestFundingRates(t.Context(), &fundingrate.LatestRateRequest{
Asset: asset.CoinMarginedFutures,
Pair: currency.NewBTCUSD(),
IncludePredictedRate: true,
})
require.NoError(t, err)
err = e.CurrencyPairs.EnablePair(asset.CoinMarginedFutures, currency.NewBTCUSD())
require.ErrorIs(t, err, currency.ErrPairAlreadyEnabled)
_, err = e.GetLatestFundingRates(t.Context(), &fundingrate.LatestRateRequest{
Asset: asset.CoinMarginedFutures,
IncludePredictedRate: true,
})
require.NoError(t, err)
}
func TestIsPerpetualFutureCurrency(t *testing.T) {
t.Parallel()
is, err := e.IsPerpetualFutureCurrency(asset.Binary, currency.NewBTCUSDT())
require.NoError(t, err)
assert.False(t, is)
is, err = e.IsPerpetualFutureCurrency(asset.CoinMarginedFutures, currency.NewBTCUSDT())
require.NoError(t, err)
assert.True(t, is)
}
func TestGetSwapFundingRates(t *testing.T) {
t.Parallel()
_, err := e.GetSwapFundingRates(t.Context())
require.NoError(t, err)
}
func TestGetBatchCoinMarginSwapContracts(t *testing.T) {
t.Parallel()
resp, err := e.GetBatchCoinMarginSwapContracts(t.Context())
assert.NoError(t, err)
assert.NotEmpty(t, resp)
}
func TestGetBatchLinearSwapContracts(t *testing.T) {
t.Parallel()
resp, err := e.GetBatchLinearSwapContracts(t.Context())
assert.NoError(t, err)
assert.NotEmpty(t, resp)
}
func TestGetBatchFuturesContracts(t *testing.T) {
t.Parallel()
resp, err := e.GetBatchFuturesContracts(t.Context())
assert.NoError(t, err)
assert.NotEmpty(t, resp)
}
func TestUpdateTickers(t *testing.T) {
t.Parallel()
updatePairsOnce(t, e)
for _, a := range e.GetAssetTypes(false) {
err := e.UpdateTickers(t.Context(), a)
require.NoErrorf(t, err, "asset %s", a)
avail, err := e.GetAvailablePairs(a)
require.NoError(t, err)
for _, p := range avail {
_, err = ticker.GetTicker(e.Name, p, a)
assert.NoErrorf(t, err, "Could not get ticker for %s %s", a, p)
}
}
}
var expiryWindows = map[string]uint{
"CW": 14,
"NW": 21,
"CQ": 190,
"NQ": 282,
}
// TestPairFromContractExpiryCode ensures at least some contract codes are available and loaded with sane dates
// Expectations are relaxed because dates are unpredictable and codes disappear intermittently
func TestPairFromContractExpiryCode(t *testing.T) {
t.Parallel()
e := new(Exchange) //nolint:govet // Intentional shadow
require.NoError(t, testexch.Setup(e), "Test Instance Setup must not fail")
_, err := e.FetchTradablePairs(t.Context(), asset.Futures)
require.NoError(t, err)
tz, err := time.LoadLocation("Asia/Singapore") // Huobi HQ and apparent local time for when codes become effective
require.NoError(t, err, "LoadLocation must not error")
today := time.Now()
today = time.Date(today.Year(), today.Month(), today.Day(), 0, 0, 0, 0, tz) // Do not use Truncate; https://github.com/golang/go/issues/55921
require.NotEmpty(t, e.futureContractCodes, "At least one contract code must be loaded")
for cType, cachedContract := range e.futureContractCodes {
t.Run(cType, func(t *testing.T) {
t.Parallel()
p, err := e.pairFromContractExpiryCode(currency.Pair{
Base: currency.BTC,
Quote: currency.NewCode(cType),
})
require.NoError(t, err)
assert.Equal(t, currency.BTC, p.Base, "pair Base should be BTC")
assert.Equal(t, cachedContract, p.Quote, "pair Quote should match futureContractCodes value")
exp, err := time.ParseInLocation("060102", p.Quote.String(), tz)
require.NoError(t, err, "currency code must be a parsable date")
require.Falsef(t, exp.Before(today), "expiry must be today or after; Got: %q", exp)
diff := uint(exp.Sub(today).Hours() / 24)
require.LessOrEqualf(t, diff, expiryWindows[cType], "expiry must be within expected update window; Today: %q, Expiry: %q",
today.Format(time.DateOnly),
exp.Format(time.DateOnly),
)
})
}
}
func TestGetOpenInterest(t *testing.T) {
t.Parallel()
updatePairsOnce(t, e)
_, err := e.GetOpenInterest(t.Context(), key.PairAsset{
Base: currency.ETH.Item,
Quote: currency.USDT.Item,
Asset: asset.USDTMarginedFutures,
})
assert.ErrorIs(t, err, asset.ErrNotSupported)
resp, err := e.GetOpenInterest(t.Context(), key.PairAsset{
Base: currency.BTC.Item,
Quote: currency.USD.Item,
Asset: asset.CoinMarginedFutures,
})
require.NoError(t, err)
assert.NotEmpty(t, resp)
resp, err = e.GetOpenInterest(t.Context(), key.PairAsset{
Base: btccwPair.Base.Item,
Quote: btccwPair.Quote.Item,
Asset: asset.Futures,
})
require.NoError(t, err)
assert.NotEmpty(t, resp)
resp, err = e.GetOpenInterest(t.Context())
require.NoError(t, err)
assert.NotEmpty(t, resp)
}
func TestContractOpenInterestUSDT(t *testing.T) {
t.Parallel()
resp, err := e.ContractOpenInterestUSDT(t.Context(), currency.EMPTYPAIR, currency.EMPTYPAIR, "", "")
assert.NoError(t, err)
assert.NotEmpty(t, resp)
cp := currency.NewBTCUSDT()
resp, err = e.ContractOpenInterestUSDT(t.Context(), cp, currency.EMPTYPAIR, "", "")
assert.NoError(t, err)
assert.NotEmpty(t, resp)
resp, err = e.ContractOpenInterestUSDT(t.Context(), currency.EMPTYPAIR, cp, "", "")
assert.NoError(t, err)
assert.NotEmpty(t, resp)
resp, err = e.ContractOpenInterestUSDT(t.Context(), cp, currency.EMPTYPAIR, "this_week", "")
assert.NoError(t, err)
assert.NotEmpty(t, resp)
resp, err = e.ContractOpenInterestUSDT(t.Context(), currency.EMPTYPAIR, currency.EMPTYPAIR, "", "swap")
assert.NoError(t, err)
assert.NotEmpty(t, resp)
}
func TestGetCurrencyTradeURL(t *testing.T) {
t.Parallel()
updatePairsOnce(t, e)
for _, a := range e.GetAssetTypes(false) {
pairs, err := e.CurrencyPairs.GetPairs(a, false)
require.NoErrorf(t, err, "cannot get pairs for %s", a)
require.NotEmptyf(t, pairs, "no pairs for %s", a)
resp, err := e.GetCurrencyTradeURL(t.Context(), a, pairs[0])
if (a == asset.Futures || a == asset.CoinMarginedFutures) && !pairs[0].Quote.Equal(currency.USD) && !pairs[0].Quote.Equal(currency.USDT) {
require.ErrorIs(t, err, common.ErrNotYetImplemented)
} else {
require.NoError(t, err)
assert.NotEmpty(t, resp)
}
}
}
func TestGenerateSubscriptions(t *testing.T) {
t.Parallel()
e := new(Exchange) //nolint:govet // Intentional shadow
require.NoError(t, testexch.Setup(e), "Test instance Setup must not error")
e.Websocket.SetCanUseAuthenticatedEndpoints(true)
subs, err := e.generateSubscriptions()
require.NoError(t, err, "generateSubscriptions must not error")
exp := subscription.List{}
for _, s := range e.Features.Subscriptions {
if s.Asset == asset.Empty {
s := s.Clone() //nolint:govet // Intentional lexical scope shadow
s.QualifiedChannel = channelName(s)
exp = append(exp, s)
continue
}
for _, a := range e.GetAssetTypes(true) {
if s.Asset != asset.All && s.Asset != a {
continue
}
pairs, err := e.GetEnabledPairs(a)
require.NoErrorf(t, err, "GetEnabledPairs %s must not error", a)
pairs = common.SortStrings(pairs).Format(currency.PairFormat{Uppercase: false, Delimiter: ""})
s := s.Clone() //nolint:govet // Intentional lexical scope shadow
s.Asset = a
if isWildcardChannel(s) {
s.Pairs = pairs
s.QualifiedChannel = channelName(s)
exp = append(exp, s)
continue
}
for i, p := range pairs {
s := s.Clone() //nolint:govet // Intentional lexical scope shadow
s.QualifiedChannel = channelName(s, p)
switch s.Channel {
case subscription.OrderbookChannel:
s.QualifiedChannel += ".step0"
case subscription.CandlesChannel:
s.QualifiedChannel += ".1min"
}
s.Pairs = pairs[i : i+1]
exp = append(exp, s)
}
}
}
testsubs.EqualLists(t, exp, subs)
}
func wsFixture(tb testing.TB, msg []byte, w *gws.Conn) error {
tb.Helper()
action, _ := jsonparser.GetString(msg, "action")
ch, _ := jsonparser.GetString(msg, "ch")
if action == "req" && ch == "auth" {
return w.WriteMessage(gws.TextMessage, []byte(`{"action":"req","code":200,"ch":"auth","data":{}}`))
}
if action == "sub" {
return w.WriteMessage(gws.TextMessage, []byte(`{"action":"sub","code":200,"ch":"`+ch+`"}`))
}
id, _ := jsonparser.GetString(msg, "id")
sub, _ := jsonparser.GetString(msg, "sub")
if id != "" && sub != "" {
return w.WriteMessage(gws.TextMessage, []byte(`{"id":"`+id+`","status":"ok","subbed":"`+sub+`"}`))
}
return fmt.Errorf("%w: %s", errors.New("Unhandled mock websocket message"), msg)
}
// TestSubscribe exercises live public subscriptions
func TestSubscribe(t *testing.T) {
t.Parallel()
e := new(Exchange) //nolint:govet // Intentional shadow
require.NoError(t, testexch.Setup(e), "Test instance Setup must not error")
subs, err := e.Features.Subscriptions.ExpandTemplates(e)
require.NoError(t, err, "ExpandTemplates must not error")
testexch.SetupWs(t, e)
err = e.Subscribe(subs)
require.NoError(t, err, "Subscribe must not error")
got := e.Websocket.GetSubscriptions()
require.Equal(t, 8, len(got), "Must get correct number of subscriptions")
for _, s := range got {
assert.Equal(t, subscription.SubscribedState, s.State())
}
}
// TestAuthSubscribe exercises mock subscriptions including private
func TestAuthSubscribe(t *testing.T) {
t.Parallel()
subCfg := e.Features.Subscriptions
h := testexch.MockWsInstance[Exchange](t, mockws.CurryWsMockUpgrader(t, wsFixture))
h.Websocket.SetCanUseAuthenticatedEndpoints(true)
subs, err := subCfg.ExpandTemplates(h)
require.NoError(t, err, "ExpandTemplates must not error")
err = h.Subscribe(subs)
require.NoError(t, err, "Subscribe must not error")
got := h.Websocket.GetSubscriptions()
require.Equal(t, 11, len(got), "Must get correct number of subscriptions")
for _, s := range got {
assert.Equal(t, subscription.SubscribedState, s.State())
}
}
func TestChannelName(t *testing.T) {
assert.Equal(t, "market.BTC-USD.kline", channelName(&subscription.Subscription{Channel: subscription.CandlesChannel}, btcusdPair))
assert.Equal(t, "trade.clearing#*#1", channelName(&subscription.Subscription{Channel: subscription.MyTradesChannel}, btcusdPair))
assert.Panics(t, func() { channelName(&subscription.Subscription{Channel: wsOrderbookChannel}, btcusdPair) })
}
func TestIsWildcardChannel(t *testing.T) {
assert.False(t, isWildcardChannel(&subscription.Subscription{Channel: subscription.CandlesChannel}))
assert.True(t, isWildcardChannel(&subscription.Subscription{Channel: subscription.MyOrdersChannel}))
assert.Panics(t, func() { channelName(&subscription.Subscription{Channel: wsOrderbookChannel}) })
}
func TestGetErrResp(t *testing.T) {
err := getErrResp([]byte(`{"status":"error","err-code":"bad-request","err-msg":"invalid topic promiscuous.drop🐻s.nearby"}`))
assert.ErrorContains(t, err, "invalid topic promiscuous.drop🐻s.nearby (bad-request)", "V1 errors should return correctly")
err = getErrResp([]byte(`{"status":"ok","subbed":"market.btcusdt.trade.detail"}`))
assert.NoError(t, err, "V1 success should not error")
err = getErrResp([]byte(`{"action":"sub","code":2001,"ch":"naughty.drop🐻s.locally","message":"invalid.ch"}`))
assert.ErrorContains(t, err, "invalid.ch (2001)", "V2 errors should return correctly")
err = getErrResp([]byte(`{"action":"sub","code":200,"ch":"orders#btcusdt","data":{}}`))
assert.NoError(t, err, "V2 success should not error")
}
func TestBootstrap(t *testing.T) {
t.Parallel()
e := new(Exchange)
require.NoError(t, testexch.Setup(e), "Test Instance Setup must not fail")
c, err := e.Bootstrap(t.Context())
require.NoError(t, err)
assert.True(t, c, "Bootstrap should return true to continue")
e.futureContractCodes = nil
e.Features.Enabled.AutoPairUpdates = false
_, err = e.Bootstrap(t.Context())
require.NoError(t, err)
require.NotNil(t, e.futureContractCodes)
}
var (
updatePairsMutex sync.Mutex
futureContractCodesCache map[string]currency.Code
)
// updatePairsOnce updates the pairs once, and ensures a future dated contract is enabled
func updatePairsOnce(tb testing.TB, h *Exchange) {
tb.Helper()
updatePairsMutex.Lock()
defer updatePairsMutex.Unlock()
testexch.UpdatePairsOnce(tb, h)
h.futureContractCodesMutex.Lock()
if len(h.futureContractCodes) == 0 {
// Restored pairs from cache, so haven't populated futureContract Codes
require.NotEmpty(tb, futureContractCodesCache, "futureContractCodesCache must not be empty")
h.futureContractCodes = futureContractCodesCache
} else {
futureContractCodesCache = h.futureContractCodes
}
h.futureContractCodesMutex.Unlock()
if btcFutureDatedPair.Equal(currency.EMPTYPAIR) {
p, err := h.pairFromContractExpiryCode(btccwPair)
require.NoError(tb, err, "pairFromContractCode must not error")
btcFutureDatedPair = p
}
err := h.CurrencyPairs.EnablePair(asset.Futures, btcFutureDatedPair) // Must enable every time we refresh the CurrencyPairs from cache
require.NoError(tb, common.ExcludeError(err, currency.ErrPairAlreadyEnabled))
}