Files
gocryptotrader/exchanges/kraken/kraken_test.go
Gareth Kirwan a2a7fed273 Huobi: Add subscription configuration (#1604)
* Huobi: Update test config format

* Huobi: Add subscription configuration

* Huobi: Add subscription documentation

* Huobi: Clarify OB sub Levels usage

* Huobi: Enable websocket for tests

* Subscriptions: Rename ErrPrivateChannelName

Rename ErrPrivateChannelName to ErrUseConstChannelName
2024-11-11 14:19:25 +11:00

1709 lines
70 KiB
Go

package kraken
import (
"context"
"encoding/json"
"errors"
"log"
"net/http"
"os"
"strings"
"testing"
"time"
"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"
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"
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"
)
var (
k *Kraken
spotTestPair = currency.NewPair(currency.XBT, currency.USD)
futuresTestPair = currency.NewPairWithDelimiter("PF", "XBTUSD", "_")
)
// Please add your own APIkeys to do correct due diligence testing.
const (
apiKey = ""
apiSecret = ""
canManipulateRealOrders = false
)
func TestMain(m *testing.M) {
k = new(Kraken)
if err := testexch.Setup(k); err != nil {
log.Fatal(err)
}
if apiKey != "" && apiSecret != "" {
k.API.AuthenticatedSupport = true
k.SetCredentials(apiKey, apiSecret, "", "", "", "")
}
os.Exit(m.Run())
}
func TestUpdateTradablePairs(t *testing.T) {
t.Parallel()
testexch.UpdatePairsOnce(t, k)
}
func TestGetCurrentServerTime(t *testing.T) {
t.Parallel()
_, err := k.GetCurrentServerTime(context.Background())
assert.NoError(t, err, "GetCurrentServerTime should not error")
}
func TestWrapperGetServerTime(t *testing.T) {
t.Parallel()
st, err := k.GetServerTime(context.Background(), asset.Spot)
require.NoError(t, err, "GetServerTime should not error")
assert.WithinRange(t, st, time.Now().Add(-24*time.Hour), time.Now().Add(24*time.Hour), "ServerTime should be within a day of now")
}
// TestUpdateOrderExecutionLimits exercises UpdateOrderExecutionLimits and GetOrderExecutionLimits
func TestUpdateOrderExecutionLimits(t *testing.T) {
t.Parallel()
err := k.UpdateOrderExecutionLimits(context.Background(), asset.Spot)
require.NoError(t, err, "UpdateOrderExecutionLimits must not error")
for _, p := range []currency.Pair{
currency.NewPair(currency.ETH, currency.USDT),
currency.NewPair(currency.XBT, currency.USDT),
} {
limits, err := k.GetOrderExecutionLimits(asset.Spot, p)
require.NoErrorf(t, err, "%s GetOrderExecutionLimits must not error", p)
assert.Positivef(t, limits.PriceStepIncrementSize, "%s PriceStepIncrementSize should be positive", p)
assert.Positivef(t, limits.MinimumBaseAmount, "%s MinimumBaseAmount should be positive", p)
}
}
func TestFetchTradablePairs(t *testing.T) {
t.Parallel()
_, err := k.FetchTradablePairs(context.Background(), asset.Futures)
assert.NoError(t, err, "FetchTradablePairs should not error")
}
func TestUpdateTicker(t *testing.T) {
t.Parallel()
testexch.UpdatePairsOnce(t, k)
_, err := k.UpdateTicker(context.Background(), spotTestPair, asset.Spot)
assert.NoError(t, err, "UpdateTicker spot asset should not error")
_, err = k.UpdateTicker(context.Background(), futuresTestPair, asset.Futures)
assert.NoError(t, err, "UpdateTicker futures asset should not error")
}
func TestUpdateTickers(t *testing.T) {
t.Parallel()
k := new(Kraken) //nolint:govet // Intentional shadow to avoid future copy/paste mistakes
require.NoError(t, testexch.Setup(k), "Test instance Setup must not error")
testexch.UpdatePairsOnce(t, k)
err := k.UpdateTickers(context.Background(), asset.Spot)
require.NoError(t, err, "UpdateTickers must not error")
ap, err := k.GetAvailablePairs(asset.Spot)
require.NoError(t, err, "GetAvailablePairs must not error")
for i := range ap {
_, err = ticker.GetTicker(k.Name, ap[i], asset.Spot)
assert.NoErrorf(t, err, "GetTicker should not error for %s", ap[i])
}
ap, err = k.GetAvailablePairs(asset.Futures)
require.NoError(t, err, "GetAvailablePairs must not error")
err = k.UpdateTickers(context.Background(), asset.Futures)
require.NoError(t, err, "UpdateTickers must not error")
for i := range ap {
_, err = ticker.GetTicker(k.Name, ap[i], asset.Futures)
assert.NoErrorf(t, err, "GetTicker should not error for %s", ap[i])
}
err = k.UpdateTickers(context.Background(), asset.Index)
assert.ErrorIs(t, err, asset.ErrNotSupported, "UpdateTickers should error correctly for asset.Index")
}
func TestUpdateOrderbook(t *testing.T) {
t.Parallel()
_, err := k.UpdateOrderbook(context.Background(), spotTestPair, asset.Spot)
assert.NoError(t, err, "UpdateOrderbook spot asset should not error")
_, err = k.UpdateOrderbook(context.Background(), futuresTestPair, asset.Futures)
assert.NoError(t, err, "UpdateOrderbook futures asset should not error")
}
func TestFuturesBatchOrder(t *testing.T) {
t.Parallel()
var data []PlaceBatchOrderData
var tempData PlaceBatchOrderData
tempData.PlaceOrderType = "meow"
tempData.OrderID = "test123"
tempData.Symbol = futuresTestPair.Lower().String()
data = append(data, tempData)
_, err := k.FuturesBatchOrder(context.Background(), data)
assert.ErrorIs(t, err, errInvalidBatchOrderType, "FuturesBatchOrder should error correctly")
sharedtestvalues.SkipTestIfCredentialsUnset(t, k, canManipulateRealOrders)
data[0].PlaceOrderType = "cancel"
_, err = k.FuturesBatchOrder(context.Background(), data)
assert.NoError(t, err, "FuturesBatchOrder should not error")
}
func TestFuturesEditOrder(t *testing.T) {
t.Parallel()
sharedtestvalues.SkipTestIfCredentialsUnset(t, k, canManipulateRealOrders)
_, err := k.FuturesEditOrder(context.Background(), "test123", "", 5.2, 1, 0)
assert.NoError(t, err, "FuturesEditOrder should not error")
}
func TestFuturesSendOrder(t *testing.T) {
t.Parallel()
sharedtestvalues.SkipTestIfCredentialsUnset(t, k, canManipulateRealOrders)
_, err := k.FuturesSendOrder(context.Background(), order.Limit, futuresTestPair, "buy", "", "", "", true, 1, 1, 0.9)
assert.NoError(t, err, "FuturesSendOrder should not error")
}
func TestFuturesCancelOrder(t *testing.T) {
t.Parallel()
sharedtestvalues.SkipTestIfCredentialsUnset(t, k, canManipulateRealOrders)
_, err := k.FuturesCancelOrder(context.Background(), "test123", "")
assert.NoError(t, err, "FuturesCancelOrder should not error")
}
func TestFuturesGetFills(t *testing.T) {
t.Parallel()
sharedtestvalues.SkipTestIfCredentialsUnset(t, k)
_, err := k.FuturesGetFills(context.Background(), time.Now().Add(-time.Hour*24))
assert.NoError(t, err, "FuturesGetFills should not error")
}
func TestFuturesTransfer(t *testing.T) {
t.Parallel()
sharedtestvalues.SkipTestIfCredentialsUnset(t, k)
_, err := k.FuturesTransfer(context.Background(), "cash", "futures", "btc", 2)
assert.NoError(t, err, "FuturesTransfer should not error")
}
func TestFuturesGetOpenPositions(t *testing.T) {
t.Parallel()
sharedtestvalues.SkipTestIfCredentialsUnset(t, k)
_, err := k.FuturesGetOpenPositions(context.Background())
assert.NoError(t, err, "FuturesGetOpenPositions should not error")
}
func TestFuturesNotifications(t *testing.T) {
t.Parallel()
sharedtestvalues.SkipTestIfCredentialsUnset(t, k)
_, err := k.FuturesNotifications(context.Background())
assert.NoError(t, err, "FuturesNotifications should not error")
}
func TestFuturesCancelAllOrders(t *testing.T) {
t.Parallel()
sharedtestvalues.SkipTestIfCredentialsUnset(t, k, canManipulateRealOrders)
_, err := k.FuturesCancelAllOrders(context.Background(), futuresTestPair)
assert.NoError(t, err, "FuturesCancelAllOrders should not error")
}
func TestGetFuturesAccountData(t *testing.T) {
t.Parallel()
sharedtestvalues.SkipTestIfCredentialsUnset(t, k)
_, err := k.GetFuturesAccountData(context.Background())
assert.NoError(t, err, "GetFuturesAccountData should not error")
}
func TestFuturesCancelAllOrdersAfter(t *testing.T) {
t.Parallel()
sharedtestvalues.SkipTestIfCredentialsUnset(t, k, canManipulateRealOrders)
_, err := k.FuturesCancelAllOrdersAfter(context.Background(), 50)
assert.NoError(t, err, "FuturesCancelAllOrdersAfter should not error")
}
func TestFuturesOpenOrders(t *testing.T) {
t.Parallel()
sharedtestvalues.SkipTestIfCredentialsUnset(t, k)
_, err := k.FuturesOpenOrders(context.Background())
assert.NoError(t, err, "FuturesOpenOrders should not error")
}
func TestFuturesRecentOrders(t *testing.T) {
t.Parallel()
sharedtestvalues.SkipTestIfCredentialsUnset(t, k)
_, err := k.FuturesRecentOrders(context.Background(), futuresTestPair)
assert.NoError(t, err, "FuturesRecentOrders should not error")
}
func TestFuturesWithdrawToSpotWallet(t *testing.T) {
t.Parallel()
sharedtestvalues.SkipTestIfCredentialsUnset(t, k, canManipulateRealOrders)
_, err := k.FuturesWithdrawToSpotWallet(context.Background(), "xbt", 5)
assert.NoError(t, err, "FuturesWithdrawToSpotWallet should not error")
}
func TestFuturesGetTransfers(t *testing.T) {
t.Parallel()
sharedtestvalues.SkipTestIfCredentialsUnset(t, k, canManipulateRealOrders)
_, err := k.FuturesGetTransfers(context.Background(), time.Now().Add(-time.Hour*24))
assert.NoError(t, err, "FuturesGetTransfers should not error")
}
func TestGetFuturesOrderbook(t *testing.T) {
t.Parallel()
_, err := k.GetFuturesOrderbook(context.Background(), futuresTestPair)
assert.NoError(t, err, "GetFuturesOrderbook should not error")
}
func TestGetFuturesMarkets(t *testing.T) {
t.Parallel()
_, err := k.GetInstruments(context.Background())
assert.NoError(t, err, "GetInstruments should not error")
}
func TestGetFuturesTickers(t *testing.T) {
t.Parallel()
_, err := k.GetFuturesTickers(context.Background())
assert.NoError(t, err, "GetFuturesTickers should not error")
}
func TestGetFuturesTradeHistory(t *testing.T) {
t.Parallel()
_, err := k.GetFuturesTradeHistory(context.Background(), futuresTestPair, time.Now().Add(-time.Hour*24))
assert.NoError(t, err, "GetFuturesTradeHistory should not error")
}
// TestGetAssets API endpoint test
func TestGetAssets(t *testing.T) {
t.Parallel()
_, err := k.GetAssets(context.Background())
assert.NoError(t, err, "GetAssets should not error")
}
func TestSeedAssetTranslator(t *testing.T) {
t.Parallel()
err := k.SeedAssets(context.TODO())
require.NoError(t, err, "SeedAssets must not error")
for from, to := range map[string]string{"XBTUSD": "XXBTZUSD", "USD": "ZUSD", "XBT": "XXBT"} {
assert.Equal(t, from, assetTranslator.LookupAltName(to), "LookupAltName should return the correct value")
assert.Equal(t, to, assetTranslator.LookupCurrency(from), "LookupCurrency should return the correct value")
}
}
func TestSeedAssets(t *testing.T) {
t.Parallel()
var a assetTranslatorStore
assert.Empty(t, a.LookupAltName("ZUSD"), "LookupAltName on unseeded store should return empty")
a.Seed("ZUSD", "USD")
assert.Equal(t, "USD", a.LookupAltName("ZUSD"), "LookupAltName should return the correct value")
a.Seed("ZUSD", "BLA")
assert.Equal(t, "USD", a.LookupAltName("ZUSD"), "Store should ignore second reseed of existing currency")
}
func TestLookupCurrency(t *testing.T) {
t.Parallel()
var a assetTranslatorStore
assert.Empty(t, a.LookupCurrency("USD"), "LookupCurrency on unseeded store should return empty")
a.Seed("ZUSD", "USD")
assert.Equal(t, "ZUSD", a.LookupCurrency("USD"), "LookupCurrency should return the correct value")
assert.Empty(t, a.LookupCurrency("EUR"), "LookupCurrency should still not return an unseeded key")
}
// TestGetAssetPairs API endpoint test
func TestGetAssetPairs(t *testing.T) {
t.Parallel()
for _, v := range []string{"fees", "leverage", "margin", ""} {
_, err := k.GetAssetPairs(context.Background(), []string{}, v)
require.NoErrorf(t, err, "GetAssetPairs %s must not error", v)
}
}
// TestGetTicker API endpoint test
func TestGetTicker(t *testing.T) {
t.Parallel()
_, err := k.GetTicker(context.Background(), spotTestPair)
assert.NoError(t, err, "GetTicker should not error")
}
// TestGetTickers API endpoint test
func TestGetTickers(t *testing.T) {
t.Parallel()
_, err := k.GetTickers(context.Background(), "LTCUSD,ETCUSD")
assert.NoError(t, err, "GetTickers should not error")
}
// TestGetOHLC API endpoint test
func TestGetOHLC(t *testing.T) {
t.Parallel()
_, err := k.GetOHLC(context.Background(), currency.NewPairWithDelimiter("XXBT", "ZUSD", ""), "1440")
assert.NoError(t, err, "GetOHLC should not error")
}
// TestGetDepth API endpoint test
func TestGetDepth(t *testing.T) {
t.Parallel()
_, err := k.GetDepth(context.Background(), spotTestPair)
assert.NoError(t, err, "GetDepth should not error")
}
// TestGetTrades API endpoint test
func TestGetTrades(t *testing.T) {
t.Parallel()
testexch.UpdatePairsOnce(t, k)
_, err := k.GetTrades(context.Background(), spotTestPair)
assert.NoError(t, err, "GetTrades should not error")
_, err = k.GetTrades(context.Background(), currency.NewPairWithDelimiter("XXX", "XXX", ""))
assert.ErrorContains(t, err, "Unknown asset pair", "GetDepth should error correctly")
}
// TestGetSpread API endpoint test
func TestGetSpread(t *testing.T) {
t.Parallel()
_, err := k.GetSpread(context.Background(), currency.NewPair(currency.BCH, currency.EUR)) // XBTUSD not in spread data
assert.NoError(t, err, "GetSpread should not error")
}
// TestGetBalance API endpoint test
func TestGetBalance(t *testing.T) {
t.Parallel()
sharedtestvalues.SkipTestIfCredentialsUnset(t, k)
_, err := k.GetBalance(context.Background())
assert.NoError(t, err, "GetBalance should not error")
}
// TestGetTradeBalance API endpoint test
func TestGetDepositMethods(t *testing.T) {
t.Parallel()
sharedtestvalues.SkipTestIfCredentialsUnset(t, k)
_, err := k.GetDepositMethods(context.Background(), "USDT")
assert.NoError(t, err, "GetDepositMethods should not error")
}
// TestGetTradeBalance API endpoint test
func TestGetTradeBalance(t *testing.T) {
t.Parallel()
sharedtestvalues.SkipTestIfCredentialsUnset(t, k)
args := TradeBalanceOptions{Asset: "ZEUR"}
_, err := k.GetTradeBalance(context.Background(), args)
assert.NoError(t, err)
}
// TestGetOpenOrders API endpoint test
func TestGetOpenOrders(t *testing.T) {
t.Parallel()
sharedtestvalues.SkipTestIfCredentialsUnset(t, k)
args := OrderInfoOptions{Trades: true}
_, err := k.GetOpenOrders(context.Background(), args)
assert.NoError(t, err)
}
// TestGetClosedOrders API endpoint test
func TestGetClosedOrders(t *testing.T) {
t.Parallel()
sharedtestvalues.SkipTestIfCredentialsUnset(t, k)
args := GetClosedOrdersOptions{Trades: true, Start: "OE4KV4-4FVQ5-V7XGPU"}
_, err := k.GetClosedOrders(context.Background(), args)
assert.NoError(t, err)
}
// TestQueryOrdersInfo API endpoint test
func TestQueryOrdersInfo(t *testing.T) {
t.Parallel()
sharedtestvalues.SkipTestIfCredentialsUnset(t, k)
args := OrderInfoOptions{Trades: true}
_, err := k.QueryOrdersInfo(context.Background(), args, "OR6ZFV-AA6TT-CKFFIW", "OAMUAJ-HLVKG-D3QJ5F")
assert.NoError(t, err)
}
// TestGetTradesHistory API endpoint test
func TestGetTradesHistory(t *testing.T) {
t.Parallel()
sharedtestvalues.SkipTestIfCredentialsUnset(t, k)
args := GetTradesHistoryOptions{Trades: true, Start: "TMZEDR-VBJN2-NGY6DX", End: "TVRXG2-R62VE-RWP3UW"}
_, err := k.GetTradesHistory(context.Background(), args)
assert.NoError(t, err)
}
// TestQueryTrades API endpoint test
func TestQueryTrades(t *testing.T) {
t.Parallel()
sharedtestvalues.SkipTestIfCredentialsUnset(t, k)
_, err := k.QueryTrades(context.Background(), true, "TMZEDR-VBJN2-NGY6DX", "TFLWIB-KTT7L-4TWR3L", "TDVRAH-2H6OS-SLSXRX")
assert.NoError(t, err)
}
// TestOpenPositions API endpoint test
func TestOpenPositions(t *testing.T) {
t.Parallel()
sharedtestvalues.SkipTestIfCredentialsUnset(t, k)
_, err := k.OpenPositions(context.Background(), false)
assert.NoError(t, err)
}
// TestGetLedgers API endpoint test
// TODO: Needs a positive test
func TestGetLedgers(t *testing.T) {
t.Parallel()
sharedtestvalues.SkipTestIfCredentialsUnset(t, k)
args := GetLedgersOptions{Start: "LRUHXI-IWECY-K4JYGO", End: "L5NIY7-JZQJD-3J4M2V", Ofs: 15}
_, err := k.GetLedgers(context.Background(), args)
assert.ErrorContains(t, err, "EQuery:Unknown asset pair", "GetLedger should error on imaginary ledgers")
}
// TestQueryLedgers API endpoint test
func TestQueryLedgers(t *testing.T) {
t.Parallel()
sharedtestvalues.SkipTestIfCredentialsUnset(t, k)
_, err := k.QueryLedgers(context.Background(), "LVTSFS-NHZVM-EXNZ5M")
assert.NoError(t, err)
}
// TestGetTradeVolume API endpoint test
func TestGetTradeVolume(t *testing.T) {
t.Parallel()
sharedtestvalues.SkipTestIfCredentialsUnset(t, k)
_, err := k.GetTradeVolume(context.Background(), true, spotTestPair)
assert.NoError(t, err, "GetTradeVolume should not error")
}
// TestOrders Tests AddOrder and CancelExistingOrder
func TestOrders(t *testing.T) {
t.Parallel()
sharedtestvalues.SkipTestIfCredentialsUnset(t, k, canManipulateRealOrders)
args := AddOrderOptions{OrderFlags: "fcib"}
cp, err := currency.NewPairFromString("XXBTZUSD")
assert.NoError(t, err, "NewPairFromString should not error")
resp, err := k.AddOrder(context.Background(),
cp,
order.Buy.Lower(), order.Limit.Lower(),
0.0001, 9000, 9000, 0, &args)
if assert.NoError(t, err, "AddOrder should not error") {
if assert.Len(t, resp.TransactionIDs, 1, "One TransactionId should be returned") {
id := resp.TransactionIDs[0]
_, err = k.CancelExistingOrder(context.Background(), id)
assert.NoErrorf(t, err, "CancelExistingOrder should not error, Please ensure order %s is cancelled manually", id)
}
}
}
// TestCancelExistingOrder API endpoint test
func TestCancelExistingOrder(t *testing.T) {
t.Parallel()
sharedtestvalues.SkipTestIfCredentialsUnset(t, k, canManipulateRealOrders)
_, err := k.CancelExistingOrder(context.Background(), "OAVY7T-MV5VK-KHDF5X")
if assert.Error(t, err, "Cancel with imaginary order-id should error") {
assert.ErrorContains(t, err, "EOrder:Unknown order", "Cancel with imaginary order-id should error Unknown Order")
}
}
func setFeeBuilder() *exchange.FeeBuilder {
return &exchange.FeeBuilder{
Amount: 1,
FeeType: exchange.CryptocurrencyTradeFee,
Pair: currency.NewPair(currency.XXBT, currency.ZUSD),
PurchasePrice: 1,
FiatCurrency: currency.USD,
BankTransactionType: exchange.WireTransfer,
}
}
// TestGetFeeByTypeOfflineTradeFee logic test
func TestGetFeeByTypeOfflineTradeFee(t *testing.T) {
t.Parallel()
var feeBuilder = setFeeBuilder()
f, err := k.GetFeeByType(context.Background(), feeBuilder)
require.NoError(t, err, "GetFeeByType must not error")
assert.Positive(t, f, "GetFeeByType should return a positive value")
if !sharedtestvalues.AreAPICredentialsSet(k) {
assert.Equal(t, exchange.OfflineTradeFee, feeBuilder.FeeType, "GetFeeByType should set FeeType correctly")
} else {
assert.Equal(t, exchange.CryptocurrencyTradeFee, feeBuilder.FeeType, "GetFeeByType should set FeeType correctly")
}
}
// TestGetFee exercises GetFee
func TestGetFee(t *testing.T) {
t.Parallel()
var feeBuilder = setFeeBuilder()
if sharedtestvalues.AreAPICredentialsSet(k) {
_, err := k.GetFee(context.Background(), feeBuilder)
assert.NoError(t, err, "CryptocurrencyTradeFee Basic GetFee should not error")
feeBuilder = setFeeBuilder()
feeBuilder.Amount = 1000
feeBuilder.PurchasePrice = 1000
_, err = k.GetFee(context.Background(), feeBuilder)
assert.NoError(t, err, "CryptocurrencyTradeFee High quantity GetFee should not error")
feeBuilder = setFeeBuilder()
feeBuilder.IsMaker = true
_, err = k.GetFee(context.Background(), feeBuilder)
assert.NoError(t, err, "CryptocurrencyTradeFee IsMaker GetFee should not error")
feeBuilder = setFeeBuilder()
feeBuilder.PurchasePrice = -1000
_, err = k.GetFee(context.Background(), feeBuilder)
assert.NoError(t, err, "CryptocurrencyTradeFee Negative purchase price GetFee should not error")
feeBuilder = setFeeBuilder()
feeBuilder.FeeType = exchange.InternationalBankDepositFee
_, err = k.GetFee(context.Background(), feeBuilder)
assert.NoError(t, err, "InternationalBankDepositFee Basic GetFee should not error")
}
feeBuilder = setFeeBuilder()
feeBuilder.FeeType = exchange.CryptocurrencyDepositFee
feeBuilder.Pair.Base = currency.XXBT
_, err := k.GetFee(context.Background(), feeBuilder)
assert.NoError(t, err, "CryptocurrencyDepositFee Basic GetFee should not error")
feeBuilder = setFeeBuilder()
feeBuilder.FeeType = exchange.CryptocurrencyWithdrawalFee
_, err = k.GetFee(context.Background(), feeBuilder)
assert.NoError(t, err, "CryptocurrencyWithdrawalFee Basic GetFee should not error")
feeBuilder = setFeeBuilder()
feeBuilder.Pair.Base = currency.NewCode("hello")
feeBuilder.FeeType = exchange.CryptocurrencyWithdrawalFee
_, err = k.GetFee(context.Background(), feeBuilder)
assert.NoError(t, err, "CryptocurrencyWithdrawalFee Invalid currency GetFee should not error")
feeBuilder = setFeeBuilder()
feeBuilder.FeeType = exchange.InternationalBankWithdrawalFee
feeBuilder.FiatCurrency = currency.USD
_, err = k.GetFee(context.Background(), feeBuilder)
assert.NoError(t, err, "InternationalBankWithdrawalFee Basic GetFee should not error")
}
// TestFormatWithdrawPermissions logic test
func TestFormatWithdrawPermissions(t *testing.T) {
t.Parallel()
exp := exchange.AutoWithdrawCryptoWithSetupText + " & " + exchange.WithdrawCryptoWith2FAText + " & " + exchange.AutoWithdrawFiatWithSetupText + " & " + exchange.WithdrawFiatWith2FAText
withdrawPermissions := k.FormatWithdrawPermissions()
assert.Equal(t, exp, withdrawPermissions, "FormatWithdrawPermissions should return correct value")
}
// TestGetActiveOrders wrapper test
func TestGetActiveOrders(t *testing.T) {
t.Parallel()
sharedtestvalues.SkipTestIfCredentialsUnset(t, k)
var getOrdersRequest = order.MultiOrderRequest{
Type: order.AnyType,
AssetType: asset.Spot,
Pairs: currency.Pairs{spotTestPair},
Side: order.AnySide,
}
_, err := k.GetActiveOrders(context.Background(), &getOrdersRequest)
assert.NoError(t, err, "GetActiveOrders should not error")
}
// TestGetOrderHistory wrapper test
func TestGetOrderHistory(t *testing.T) {
t.Parallel()
sharedtestvalues.SkipTestIfCredentialsUnset(t, k)
var getOrdersRequest = order.MultiOrderRequest{
Type: order.AnyType,
AssetType: asset.Spot,
Side: order.AnySide,
}
_, err := k.GetOrderHistory(context.Background(), &getOrdersRequest)
assert.NoError(t, err)
}
// TestGetOrderInfo exercises GetOrderInfo
func TestGetOrderInfo(t *testing.T) {
t.Parallel()
sharedtestvalues.SkipTestIfCredentialsUnset(t, k)
_, err := k.GetOrderInfo(context.Background(), "OZPTPJ-HVYHF-EDIGXS", currency.EMPTYPAIR, asset.Spot)
assert.ErrorContains(t, err, "order OZPTPJ-HVYHF-EDIGXS not found in response", "Should error that order was not found in response")
}
// Any tests below this line have the ability to impact your orders on the exchange. Enable canManipulateRealOrders to run them
// ----------------------------------------------------------------------------------------------------------------------------
// TestSubmitOrder wrapper test
func TestSubmitOrder(t *testing.T) {
t.Parallel()
sharedtestvalues.SkipTestIfCannotManipulateOrders(t, k, canManipulateRealOrders)
var orderSubmission = &order.Submit{
Exchange: k.Name,
Pair: spotTestPair,
Side: order.Buy,
Type: order.Limit,
Price: 1,
Amount: 1,
ClientID: "meowOrder",
AssetType: asset.Spot,
}
response, err := k.SubmitOrder(context.Background(), orderSubmission)
if sharedtestvalues.AreAPICredentialsSet(k) {
assert.NoError(t, err, "SubmitOrder should not error")
assert.Equal(t, order.New, response.Status, "SubmitOrder should return a New order status")
} else {
assert.ErrorIs(t, err, exchange.ErrAuthenticationSupportNotEnabled, "SubmitOrder should error correctly")
}
}
// TestCancelExchangeOrder wrapper test
func TestCancelExchangeOrder(t *testing.T) {
t.Parallel()
err := k.CancelOrder(context.Background(), &order.Cancel{
AssetType: asset.Options,
OrderID: "1337",
})
assert.ErrorIs(t, err, asset.ErrNotSupported, "CancelOrder should error on Options asset")
sharedtestvalues.SkipTestIfCannotManipulateOrders(t, k, canManipulateRealOrders)
var orderCancellation = &order.Cancel{
OrderID: "OGEX6P-B5Q74-IGZ72R",
AssetType: asset.Spot,
}
err = k.CancelOrder(context.Background(), orderCancellation)
if sharedtestvalues.AreAPICredentialsSet(k) {
assert.NoError(t, err, "CancelOrder should not error")
} else {
assert.ErrorIs(t, err, exchange.ErrAuthenticationSupportNotEnabled, "CancelOrder should error correctly")
}
}
// TestCancelExchangeOrder wrapper test
func TestCancelBatchExchangeOrder(t *testing.T) {
t.Parallel()
sharedtestvalues.SkipTestIfCannotManipulateOrders(t, k, canManipulateRealOrders)
var ordersCancellation []order.Cancel
ordersCancellation = append(ordersCancellation, order.Cancel{
Pair: currency.NewPairWithDelimiter(currency.BTC.String(), currency.USD.String(), "/"),
OrderID: "OGEX6P-B5Q74-IGZ72R,OGEX6P-B5Q74-IGZ722",
AssetType: asset.Spot,
})
_, err := k.CancelBatchOrders(context.Background(), ordersCancellation)
if sharedtestvalues.AreAPICredentialsSet(k) {
assert.NoError(t, err, "CancelBatchOrder should not error")
} else {
assert.ErrorIs(t, err, common.ErrFunctionNotSupported, "CancelBatchOrders should error correctly")
}
}
// TestCancelAllExchangeOrders wrapper test
func TestCancelAllExchangeOrders(t *testing.T) {
t.Parallel()
sharedtestvalues.SkipTestIfCannotManipulateOrders(t, k, canManipulateRealOrders)
resp, err := k.CancelAllOrders(context.Background(), &order.Cancel{AssetType: asset.Spot})
if sharedtestvalues.AreAPICredentialsSet(k) {
assert.NoError(t, err, "CancelAllOrders should not error")
} else {
assert.ErrorIs(t, err, exchange.ErrAuthenticationSupportNotEnabled, "CancelBatchOrders should error correctly")
}
assert.Empty(t, resp.Status, "CancelAllOrders Status should not contain any failed order errors")
}
// TestUpdateAccountInfo exercises UpdateAccountInfo
func TestUpdateAccountInfo(t *testing.T) {
t.Parallel()
for _, a := range []asset.Item{asset.Spot, asset.Futures} {
_, err := k.UpdateAccountInfo(context.Background(), a)
if sharedtestvalues.AreAPICredentialsSet(k) {
assert.NoErrorf(t, err, "UpdateAccountInfo should not error for asset %s", a) // Note Well: Spot and Futures have separate api keys
} else {
assert.ErrorIsf(t, err, exchange.ErrAuthenticationSupportNotEnabled, "UpdateAccountInfo should error correctly for asset %s", a)
}
}
}
// TestModifyOrder wrapper test
func TestModifyOrder(t *testing.T) {
t.Parallel()
_, err := k.ModifyOrder(context.Background(), &order.Modify{AssetType: asset.Spot})
assert.ErrorIs(t, err, common.ErrFunctionNotSupported, "ModifyOrder should error correctly")
}
// TestWithdraw wrapper test
func TestWithdraw(t *testing.T) {
t.Parallel()
sharedtestvalues.SkipTestIfCannotManipulateOrders(t, k, canManipulateRealOrders)
withdrawCryptoRequest := withdraw.Request{
Exchange: k.Name,
Crypto: withdraw.CryptoRequest{
Address: core.BitcoinDonationAddress,
},
Amount: -1,
Currency: currency.XXBT,
Description: "WITHDRAW IT ALL",
TradePassword: "Key",
}
_, err := k.WithdrawCryptocurrencyFunds(context.Background(),
&withdrawCryptoRequest)
if !sharedtestvalues.AreAPICredentialsSet(k) && err == nil {
t.Error("Expecting an error when no keys are set")
}
if sharedtestvalues.AreAPICredentialsSet(k) && err != nil {
t.Errorf("Withdraw failed to be placed: %v", err)
}
}
// TestWithdrawFiat wrapper test
func TestWithdrawFiat(t *testing.T) {
t.Parallel()
sharedtestvalues.SkipTestIfCannotManipulateOrders(t, k, canManipulateRealOrders)
var withdrawFiatRequest = withdraw.Request{
Amount: -1,
Currency: currency.EUR,
Description: "WITHDRAW IT ALL",
TradePassword: "someBank",
}
_, err := k.WithdrawFiatFunds(context.Background(), &withdrawFiatRequest)
if !sharedtestvalues.AreAPICredentialsSet(k) && err == nil {
t.Error("Expecting an error when no keys are set")
}
if sharedtestvalues.AreAPICredentialsSet(k) && err != nil {
t.Errorf("Withdraw failed to be placed: %v", err)
}
}
// TestWithdrawInternationalBank wrapper test
func TestWithdrawInternationalBank(t *testing.T) {
t.Parallel()
sharedtestvalues.SkipTestIfCannotManipulateOrders(t, k, canManipulateRealOrders)
var withdrawFiatRequest = withdraw.Request{
Amount: -1,
Currency: currency.EUR,
Description: "WITHDRAW IT ALL",
TradePassword: "someBank",
}
_, err := k.WithdrawFiatFundsToInternationalBank(context.Background(),
&withdrawFiatRequest)
if !sharedtestvalues.AreAPICredentialsSet(k) && err == nil {
t.Error("Expecting an error when no keys are set")
}
if sharedtestvalues.AreAPICredentialsSet(k) && err != nil {
t.Errorf("Withdraw failed to be placed: %v", err)
}
}
func TestGetCryptoDepositAddress(t *testing.T) {
t.Parallel()
sharedtestvalues.SkipTestIfCredentialsUnset(t, k)
_, err := k.GetCryptoDepositAddress(context.Background(), "Bitcoin", "XBT", false)
if err != nil {
t.Error(err)
}
if !canManipulateRealOrders {
t.Skip("canManipulateRealOrders not set, skipping test")
}
_, err = k.GetCryptoDepositAddress(context.Background(), "Bitcoin", "XBT", true)
if err != nil {
t.Error(err)
}
}
// TestGetDepositAddress wrapper test
func TestGetDepositAddress(t *testing.T) {
t.Parallel()
if sharedtestvalues.AreAPICredentialsSet(k) {
_, err := k.GetDepositAddress(context.Background(), currency.USDT, "", "")
if err != nil {
t.Error("GetDepositAddress() error", err)
}
} else {
_, err := k.GetDepositAddress(context.Background(), currency.BTC, "", "")
if err == nil {
t.Error("GetDepositAddress() error can not be nil")
}
}
}
// TestWithdrawStatus wrapper test
func TestWithdrawStatus(t *testing.T) {
t.Parallel()
if sharedtestvalues.AreAPICredentialsSet(k) {
_, err := k.WithdrawStatus(context.Background(), currency.BTC, "")
if err != nil {
t.Error("WithdrawStatus() error", err)
}
} else {
_, err := k.WithdrawStatus(context.Background(), currency.BTC, "")
if err == nil {
t.Error("GetDepositAddress() error can not be nil")
}
}
}
// TestWithdrawCancel wrapper test
func TestWithdrawCancel(t *testing.T) {
t.Parallel()
_, err := k.WithdrawCancel(context.Background(), currency.BTC, "")
if sharedtestvalues.AreAPICredentialsSet(k) && err == nil {
t.Error("WithdrawCancel() error cannot be nil")
} else if !sharedtestvalues.AreAPICredentialsSet(k) && err == nil {
t.Errorf("WithdrawCancel() error - expecting an error when no keys are set but received nil")
}
}
// ---------------------------- Websocket tests -----------------------------------------
// TestWsSubscribe tests unauthenticated websocket subscriptions
// Specifically looking to ensure multiple errors are collected and returned and ws.Subscriptions Added/Removed in cases of:
// single pass, single fail, mixed fail, multiple pass, all fail
// No objection to this becoming a fixture test, so long as it integrates through Un/Subscribe roundtrip
func TestWsSubscribe(t *testing.T) {
k := new(Kraken) //nolint:govet // Intentional shadow to avoid future copy/paste mistakes
require.NoError(t, testexch.Setup(k), "Setup Instance must not error")
testexch.SetupWs(t, k)
for _, enabled := range []bool{false, true} {
require.NoError(t, k.SetPairs(currency.Pairs{
spotTestPair,
currency.NewPairWithDelimiter("ETH", "USD", "/"),
currency.NewPairWithDelimiter("LTC", "ETH", "/"),
currency.NewPairWithDelimiter("ETH", "XBT", "/"),
// Enable pairs that won't error locally, so we get upstream errors to test error combinations
currency.NewPairWithDelimiter("DWARF", "HOBBIT", "/"),
currency.NewPairWithDelimiter("DWARF", "GOBLIN", "/"),
currency.NewPairWithDelimiter("DWARF", "ELF", "/"),
}, asset.Spot, enabled), "SetPairs must not error")
}
err := k.Subscribe(subscription.List{{Asset: asset.Spot, Channel: subscription.TickerChannel, Pairs: currency.Pairs{spotTestPair}}})
require.NoError(t, err, "Simple subscription must not error")
subs := k.Websocket.GetSubscriptions()
require.Len(t, subs, 1, "Should add 1 Subscription")
assert.Equal(t, subscription.SubscribedState, subs[0].State(), "Subscription should be subscribed state")
err = k.Subscribe(subscription.List{{Asset: asset.Spot, Channel: subscription.TickerChannel, Pairs: currency.Pairs{spotTestPair}}})
assert.ErrorIs(t, err, subscription.ErrDuplicate, "Resubscribing to the same channel should error with SubscribedAlready")
subs = k.Websocket.GetSubscriptions()
require.Len(t, subs, 1, "Should not add a subscription on error")
assert.Equal(t, subscription.SubscribedState, subs[0].State(), "Existing subscription state should not change")
err = k.Subscribe(subscription.List{{Asset: asset.Spot, Channel: subscription.TickerChannel, Pairs: currency.Pairs{currency.NewPairWithDelimiter("DWARF", "HOBBIT", "/")}}})
assert.ErrorContains(t, err, "Currency pair not supported; Channel: ticker Pairs: DWARF/HOBBIT", "Subscribing to an invalid pair should error correctly")
require.Len(t, k.Websocket.GetSubscriptions(), 1, "Should not add a subscription on error")
// Mix success and failure
err = k.Subscribe(subscription.List{
{Asset: asset.Spot, Channel: subscription.TickerChannel, Pairs: currency.Pairs{currency.NewPairWithDelimiter("ETH", "USD", "/")}},
{Asset: asset.Spot, Channel: subscription.TickerChannel, Pairs: currency.Pairs{currency.NewPairWithDelimiter("DWARF", "HOBBIT", "/")}},
{Asset: asset.Spot, Channel: subscription.TickerChannel, Pairs: currency.Pairs{currency.NewPairWithDelimiter("DWARF", "ELF", "/")}},
})
assert.ErrorContains(t, err, "Currency pair not supported; Channel: ticker Pairs:", "Subscribing to an invalid pair should error correctly")
assert.ErrorContains(t, err, "DWARF/HOBBIT", "Subscribing to an invalid pair should error correctly")
assert.ErrorContains(t, err, "DWARF/ELF", "Subscribing to an invalid pair should error correctly")
require.Len(t, k.Websocket.GetSubscriptions(), 2, "Should have 2 subscriptions after mixed success/failures")
// Just failures
err = k.Subscribe(subscription.List{
{Asset: asset.Spot, Channel: subscription.TickerChannel, Pairs: currency.Pairs{currency.NewPairWithDelimiter("DWARF", "HOBBIT", "/")}},
{Asset: asset.Spot, Channel: subscription.TickerChannel, Pairs: currency.Pairs{currency.NewPairWithDelimiter("DWARF", "GOBLIN", "/")}},
})
assert.ErrorContains(t, err, "Currency pair not supported; Channel: ticker Pairs:", "Subscribing to an invalid pair should error correctly")
assert.ErrorContains(t, err, "DWARF/HOBBIT", "Subscribing to an invalid pair should error correctly")
assert.ErrorContains(t, err, "DWARF/GOBLIN", "Subscribing to an invalid pair should error correctly")
require.Len(t, k.Websocket.GetSubscriptions(), 2, "Should have 2 subscriptions after mixed success/failures")
// Just success
err = k.Subscribe(subscription.List{
{Asset: asset.Spot, Channel: subscription.TickerChannel, Pairs: currency.Pairs{currency.NewPairWithDelimiter("ETH", "XBT", "/")}},
{Asset: asset.Spot, Channel: subscription.TickerChannel, Pairs: currency.Pairs{currency.NewPairWithDelimiter("LTC", "ETH", "/")}},
})
assert.NoError(t, err, "Multiple successful subscriptions should not error")
subs = k.Websocket.GetSubscriptions()
assert.Len(t, subs, 4, "Should have correct number of subscriptions")
err = k.Unsubscribe(subs[:1])
assert.NoError(t, err, "Simple Unsubscribe should succeed")
assert.Len(t, k.Websocket.GetSubscriptions(), 3, "Should have removed 1 channel")
err = k.Unsubscribe(subscription.List{{Channel: subscription.TickerChannel, Pairs: currency.Pairs{currency.NewPairWithDelimiter("DWARF", "WIZARD", "/")}, Key: 1337}})
assert.ErrorIs(t, err, subscription.ErrNotFound, "Simple failing Unsubscribe should error NotFound")
assert.ErrorContains(t, err, "DWARF/WIZARD", "Unsubscribing from an invalid pair should error correctly")
assert.Len(t, k.Websocket.GetSubscriptions(), 3, "Should not have removed any channels")
err = k.Unsubscribe(subscription.List{
subs[1],
{Asset: asset.Spot, Channel: subscription.TickerChannel, Pairs: currency.Pairs{currency.NewPairWithDelimiter("DWARF", "EAGLE", "/")}, Key: 1338},
})
assert.ErrorIs(t, err, subscription.ErrNotFound, "Mixed failing Unsubscribe should error NotFound")
assert.ErrorContains(t, err, "Channel: ticker Pairs: DWARF/EAGLE", "Unsubscribing from an invalid pair should error correctly")
subs = k.Websocket.GetSubscriptions()
assert.Len(t, subs, 2, "Should have removed only 1 more channel")
err = k.Unsubscribe(subs)
assert.NoError(t, err, "Unsubscribe multiple passing subscriptions should not error")
assert.Empty(t, k.Websocket.GetSubscriptions(), "Should have successfully removed all channels")
for _, c := range []string{"ohlc", "ohlc-5"} {
err = k.Subscribe(subscription.List{{
Asset: asset.Spot,
Channel: c,
Pairs: currency.Pairs{spotTestPair},
}})
assert.ErrorIs(t, err, subscription.ErrUseConstChannelName, "Must error when trying to use a private channel name")
assert.ErrorContains(t, err, c+" => subscription.CandlesChannel", "Must error when trying to use a private channel name")
}
}
// TestWsResubscribe tests websocket resubscription
func TestWsResubscribe(t *testing.T) {
k := new(Kraken) //nolint:govet // Intentional shadow to avoid future copy/paste mistakes
require.NoError(t, testexch.Setup(k), "TestInstance must not error")
testexch.SetupWs(t, k)
err := k.Subscribe(subscription.List{{Asset: asset.Spot, Channel: subscription.OrderbookChannel, Levels: 1000}})
require.NoError(t, err, "Subscribe must not error")
subs := k.Websocket.GetSubscriptions()
require.Len(t, subs, 1, "Should add 1 Subscription")
require.Equal(t, subscription.SubscribedState, subs[0].State(), "Subscription should be subscribed state")
require.Eventually(t, func() bool {
b, e2 := k.Websocket.Orderbook.GetOrderbook(spotTestPair, asset.Spot)
if e2 == nil {
return !b.LastUpdated.IsZero()
}
return false
}, time.Second*4, time.Millisecond*10, "orderbook must start streaming")
// Set the state to Unsub so we definitely know Resub worked
err = subs[0].SetState(subscription.UnsubscribingState)
require.NoError(t, err)
err = k.Websocket.ResubscribeToChannel(k.Websocket.Conn, subs[0])
require.NoError(t, err, "Resubscribe must not error")
require.Equal(t, subscription.SubscribedState, subs[0].State(), "subscription must be subscribed again")
}
// TestWsOrderbookSub tests orderbook subscriptions for MaxDepth params
func TestWsOrderbookSub(t *testing.T) {
t.Parallel()
k := new(Kraken) //nolint:govet // Intentional shadow to avoid future copy/paste mistakes
require.NoError(t, testexch.Setup(k), "Setup Instance must not error")
testexch.SetupWs(t, k)
err := k.Subscribe(subscription.List{{
Asset: asset.Spot,
Channel: subscription.OrderbookChannel,
Pairs: currency.Pairs{spotTestPair},
Levels: 25,
}})
require.NoError(t, err, "Simple subscription should not error")
subs := k.Websocket.GetSubscriptions()
require.Equal(t, 1, len(subs), "Should have 1 subscription channel")
err = k.Unsubscribe(subs)
assert.NoError(t, err, "Unsubscribe should not error")
assert.Empty(t, k.Websocket.GetSubscriptions(), "Should have successfully removed all channels")
err = k.Subscribe(subscription.List{{
Asset: asset.Spot,
Channel: subscription.OrderbookChannel,
Pairs: currency.Pairs{spotTestPair},
Levels: 42,
}})
assert.ErrorContains(t, err, "Subscription depth not supported", "Bad subscription should error about depth")
}
// TestWsCandlesSub tests candles subscription for Timeframe params
func TestWsCandlesSub(t *testing.T) {
t.Parallel()
k := new(Kraken) //nolint:govet // Intentional shadow to avoid future copy/paste mistakes
require.NoError(t, testexch.Setup(k), "Setup Instance must not error")
testexch.SetupWs(t, k)
err := k.Subscribe(subscription.List{{
Asset: asset.Spot,
Channel: subscription.CandlesChannel,
Pairs: currency.Pairs{spotTestPair},
Interval: kline.OneHour,
}})
require.NoError(t, err, "Simple subscription should not error")
subs := k.Websocket.GetSubscriptions()
require.Equal(t, 1, len(subs), "Should add 1 Subscription")
err = k.Unsubscribe(subs)
assert.NoError(t, err, "Unsubscribe should not error")
assert.Empty(t, k.Websocket.GetSubscriptions(), "Should have successfully removed all channels")
err = k.Subscribe(subscription.List{{
Asset: asset.Spot,
Channel: subscription.CandlesChannel,
Pairs: currency.Pairs{spotTestPair},
Interval: kline.Interval(time.Minute * time.Duration(127)),
}})
assert.ErrorContains(t, err, "Subscription ohlc interval not supported", "Bad subscription should error about interval")
}
// TestWsOwnTradesSub tests the authenticated WS subscription channel for trades
func TestWsOwnTradesSub(t *testing.T) {
t.Parallel()
sharedtestvalues.SkipTestIfCredentialsUnset(t, k)
k := new(Kraken) //nolint:govet // Intentional shadow to avoid future copy/paste mistakes
require.NoError(t, testexch.Setup(k), "Setup Instance must not error")
testexch.SetupWs(t, k)
err := k.Subscribe(subscription.List{{Channel: subscription.MyTradesChannel, Authenticated: true}})
assert.NoError(t, err, "Subsrcibing to ownTrades should not error")
subs := k.Websocket.GetSubscriptions()
assert.Len(t, subs, 1, "Should add 1 Subscription")
err = k.Unsubscribe(subs)
assert.NoError(t, err, "Unsubscribing an auth channel should not error")
assert.Empty(t, k.Websocket.GetSubscriptions(), "Should have successfully removed channel")
}
// TestGenerateSubscriptions tests the subscriptions generated from configuration
func TestGenerateSubscriptions(t *testing.T) {
t.Parallel()
pairs, err := k.GetEnabledPairs(asset.Spot)
require.NoError(t, err, "GetEnabledPairs must not error")
require.False(t, k.Websocket.CanUseAuthenticatedEndpoints(), "Websocket must not be authenticated by default")
exp := subscription.List{
{Channel: subscription.TickerChannel},
{Channel: subscription.AllTradesChannel},
{Channel: subscription.CandlesChannel, Interval: kline.OneMin},
{Channel: subscription.OrderbookChannel, Levels: 1000},
}
for _, s := range exp {
s.QualifiedChannel = channelName(s)
s.Asset = asset.Spot
s.Pairs = pairs
}
subs, err := k.generateSubscriptions()
require.NoError(t, err, "generateSubscriptions should not error")
testsubs.EqualLists(t, exp, subs)
k.Websocket.SetCanUseAuthenticatedEndpoints(true)
exp = append(exp, subscription.List{
{Channel: subscription.MyOrdersChannel, QualifiedChannel: krakenWsOpenOrders},
{Channel: subscription.MyTradesChannel, QualifiedChannel: krakenWsOwnTrades},
}...)
subs, err = k.generateSubscriptions()
require.NoError(t, err, "generateSubscriptions should not error")
testsubs.EqualLists(t, exp, subs)
}
func TestGetWSToken(t *testing.T) {
t.Parallel()
sharedtestvalues.SkipTestIfCredentialsUnset(t, k)
k := new(Kraken) //nolint:govet // Intentional shadow to avoid future copy/paste mistakes
require.NoError(t, testexch.Setup(k), "Setup Instance must not error")
testexch.SetupWs(t, k)
resp, err := k.GetWebsocketToken(context.Background())
require.NoError(t, err, "GetWebsocketToken must not error")
assert.NotEmpty(t, resp, "Token should not be empty")
}
// TestWsAddOrder exercises roundtrip of wsAddOrder; See also: mockWsAddOrder
func TestWsAddOrder(t *testing.T) {
t.Parallel()
k := testexch.MockWsInstance[Kraken](t, curryWsMockUpgrader(t, mockWsServer)) //nolint:govet // Intentional shadow to avoid future copy/paste mistakes
require.True(t, k.IsWebsocketAuthenticationSupported(), "WS must be authenticated")
id, err := k.wsAddOrder(&WsAddOrderRequest{
OrderType: order.Limit.Lower(),
OrderSide: order.Buy.Lower(),
Pair: "XBT/USD",
Price: 80000,
})
require.NoError(t, err, "wsAddOrder must not error")
assert.Equal(t, "ONPNXH-KMKMU-F4MR5V", id, "wsAddOrder should return correct order ID")
}
// TestWsCancelOrders exercises roundtrip of wsCancelOrders; See also: mockWsCancelOrders
func TestWsCancelOrders(t *testing.T) {
t.Parallel()
k := testexch.MockWsInstance[Kraken](t, curryWsMockUpgrader(t, mockWsServer)) //nolint:govet // Intentional shadow to avoid future copy/paste mistakes
require.True(t, k.IsWebsocketAuthenticationSupported(), "WS must be authenticated")
err := k.wsCancelOrders([]string{"RABBIT", "BATFISH", "SQUIRREL", "CATFISH", "MOUSE"})
assert.ErrorIs(t, err, errCancellingOrder, "Should error cancelling order")
assert.ErrorContains(t, err, "BATFISH", "Should error containing txn id")
assert.ErrorContains(t, err, "CATFISH", "Should error containing txn id")
assert.ErrorContains(t, err, "[EOrder:Unknown order]", "Should error containing server error")
err = k.wsCancelOrders([]string{"RABBIT", "SQUIRREL", "MOUSE"})
assert.NoError(t, err, "Should not error with valid ids")
}
func TestWsCancelAllOrders(t *testing.T) {
sharedtestvalues.SkipTestIfCredentialsUnset(t, k, canManipulateRealOrders)
testexch.SetupWs(t, k)
_, err := k.wsCancelAllOrders()
require.NoError(t, err, "wsCancelAllOrders must not error")
}
func TestWsHandleData(t *testing.T) {
t.Parallel()
k := new(Kraken) //nolint:govet // Intentional shadow to avoid future copy/paste mistakes
require.NoError(t, testexch.Setup(k), "Setup Instance must not error")
for _, l := range []int{10, 100} {
err := k.Websocket.AddSuccessfulSubscriptions(k.Websocket.Conn, &subscription.Subscription{
Channel: subscription.OrderbookChannel,
Pairs: currency.Pairs{spotTestPair},
Asset: asset.Spot,
Levels: l,
})
require.NoError(t, err, "AddSuccessfulSubscriptions must not error")
}
testexch.FixtureToDataHandler(t, "testdata/wsHandleData.json", k.wsHandleData)
}
func TestWsOpenOrders(t *testing.T) {
t.Parallel()
k := new(Kraken) //nolint:govet // Intentional shadow to avoid future copy/paste mistakes
require.NoError(t, testexch.Setup(k), "Test instance Setup must not error")
testexch.UpdatePairsOnce(t, k)
testexch.FixtureToDataHandler(t, "testdata/wsOpenTrades.json", k.wsHandleData)
close(k.Websocket.DataHandler)
assert.Len(t, k.Websocket.DataHandler, 7, "Should see 7 orders")
for resp := range k.Websocket.DataHandler {
switch v := resp.(type) {
case *order.Detail:
switch len(k.Websocket.DataHandler) {
case 6:
assert.Equal(t, "OGTT3Y-C6I3P-XRI6HR", v.OrderID, "OrderID")
assert.Equal(t, order.Limit, v.Type, "order type")
assert.Equal(t, order.Sell, v.Side, "order side")
assert.Equal(t, order.Open, v.Status, "order status")
assert.Equal(t, 34.5, v.Price, "price")
assert.Equal(t, 10.00345345, v.Amount, "amount")
case 5:
assert.Equal(t, "OKB55A-UEMMN-YUXM2A", v.OrderID, "OrderID")
assert.Equal(t, order.Market, v.Type, "order type")
assert.Equal(t, order.Buy, v.Side, "order side")
assert.Equal(t, order.Pending, v.Status, "order status")
assert.Equal(t, 0.0, v.Price, "price")
assert.Equal(t, 0.0001, v.Amount, "amount")
assert.Equal(t, time.UnixMicro(1692851641361371).UTC(), v.Date, "Date")
case 4:
assert.Equal(t, "OKB55A-UEMMN-YUXM2A", v.OrderID, "OrderID")
assert.Equal(t, order.Open, v.Status, "order status")
case 3:
assert.Equal(t, "OKB55A-UEMMN-YUXM2A", v.OrderID, "OrderID")
assert.Equal(t, order.UnknownStatus, v.Status, "order status")
assert.Equal(t, 26425.2, v.AverageExecutedPrice, "AverageExecutedPrice")
assert.Equal(t, 0.0001, v.ExecutedAmount, "ExecutedAmount")
assert.Equal(t, 0.0, v.RemainingAmount, "RemainingAmount") // Not in the message; Testing regression to bad derivation
assert.Equal(t, 0.00687, v.Fee, "Fee")
case 2:
assert.Equal(t, "OKB55A-UEMMN-YUXM2A", v.OrderID, "OrderID")
assert.Equal(t, order.Closed, v.Status, "order status")
assert.Equal(t, 0.0001, v.ExecutedAmount, "ExecutedAmount")
assert.Equal(t, 26425.2, v.AverageExecutedPrice, "AverageExecutedPrice")
assert.Equal(t, 0.00687, v.Fee, "Fee")
assert.Equal(t, time.UnixMicro(1692851641361447).UTC(), v.LastUpdated, "LastUpdated")
case 1:
assert.Equal(t, "OGTT3Y-C6I3P-XRI6HR", v.OrderID, "OrderID")
assert.Equal(t, order.UnknownStatus, v.Status, "order status")
assert.Equal(t, 10.00345345, v.ExecutedAmount, "ExecutedAmount")
assert.Equal(t, 0.001, v.Fee, "Fee")
assert.Equal(t, 34.5, v.AverageExecutedPrice, "AverageExecutedPrice")
case 0:
assert.Equal(t, "OGTT3Y-C6I3P-XRI6HR", v.OrderID, "OrderID")
assert.Equal(t, order.Closed, v.Status, "order status")
assert.Equal(t, time.UnixMicro(1692675961789052).UTC(), v.LastUpdated, "LastUpdated")
assert.Equal(t, 10.00345345, v.ExecutedAmount, "ExecutedAmount")
assert.Equal(t, 0.001, v.Fee, "Fee")
assert.Equal(t, 34.5, v.AverageExecutedPrice, "AverageExecutedPrice")
}
case error:
t.Error(v)
default:
t.Errorf("Unexpected type in DataHandler: %T (%s)", v, v)
}
}
}
func TestGetHistoricCandles(t *testing.T) {
t.Parallel()
testexch.UpdatePairsOnce(t, k)
_, err := k.GetHistoricCandles(context.Background(), spotTestPair, asset.Spot, kline.OneHour, time.Now().Add(-time.Hour*12), time.Now())
assert.NoError(t, err, "GetHistoricCandles should not error")
_, err = k.GetHistoricCandles(context.Background(), futuresTestPair, asset.Futures, kline.OneHour, time.Now().Add(-time.Hour*12), time.Now())
assert.ErrorIs(t, err, asset.ErrNotSupported, "GetHistoricCandles should error with asset.ErrNotSupported")
}
func TestGetHistoricCandlesExtended(t *testing.T) {
t.Parallel()
_, err := k.GetHistoricCandlesExtended(context.Background(), futuresTestPair, asset.Spot, kline.OneMin, time.Now().Add(-time.Minute*3), time.Now())
assert.ErrorIs(t, err, common.ErrFunctionNotSupported, "GetHistoricCandlesExtended should error correctly")
}
func Test_FormatExchangeKlineInterval(t *testing.T) {
t.Parallel()
for _, tt := range []struct {
interval kline.Interval
exp string
}{
{kline.OneMin, "1"},
{kline.OneDay, "1440"},
} {
assert.Equalf(t, tt.exp, k.FormatExchangeKlineInterval(tt.interval), "FormatExchangeKlineInterval should return correct output for %s", tt.interval.Short())
}
}
func TestGetRecentTrades(t *testing.T) {
t.Parallel()
testexch.UpdatePairsOnce(t, k)
_, err := k.GetRecentTrades(context.Background(), spotTestPair, asset.Spot)
assert.NoError(t, err, "GetRecentTrades should not error")
_, err = k.GetRecentTrades(context.Background(), futuresTestPair, asset.Futures)
assert.NoError(t, err, "GetRecentTrades should not error")
}
func TestGetHistoricTrades(t *testing.T) {
t.Parallel()
_, err := k.GetHistoricTrades(context.Background(), spotTestPair, asset.Spot, time.Now().Add(-time.Minute*15), time.Now())
assert.ErrorIs(t, err, common.ErrFunctionNotSupported, "GetHistoricTrades should error")
}
var testOb = orderbook.Base{
Asks: []orderbook.Tranche{
// NOTE: 0.00000500 float64 == 0.000005
{Price: 0.05005, StrPrice: "0.05005", Amount: 0.00000500, StrAmount: "0.00000500"},
{Price: 0.05010, StrPrice: "0.05010", Amount: 0.00000500, StrAmount: "0.00000500"},
{Price: 0.05015, StrPrice: "0.05015", Amount: 0.00000500, StrAmount: "0.00000500"},
{Price: 0.05020, StrPrice: "0.05020", Amount: 0.00000500, StrAmount: "0.00000500"},
{Price: 0.05025, StrPrice: "0.05025", Amount: 0.00000500, StrAmount: "0.00000500"},
{Price: 0.05030, StrPrice: "0.05030", Amount: 0.00000500, StrAmount: "0.00000500"},
{Price: 0.05035, StrPrice: "0.05035", Amount: 0.00000500, StrAmount: "0.00000500"},
{Price: 0.05040, StrPrice: "0.05040", Amount: 0.00000500, StrAmount: "0.00000500"},
{Price: 0.05045, StrPrice: "0.05045", Amount: 0.00000500, StrAmount: "0.00000500"},
{Price: 0.05050, StrPrice: "0.05050", Amount: 0.00000500, StrAmount: "0.00000500"},
},
Bids: []orderbook.Tranche{
{Price: 0.05000, StrPrice: "0.05000", Amount: 0.00000500, StrAmount: "0.00000500"},
{Price: 0.04995, StrPrice: "0.04995", Amount: 0.00000500, StrAmount: "0.00000500"},
{Price: 0.04990, StrPrice: "0.04990", Amount: 0.00000500, StrAmount: "0.00000500"},
{Price: 0.04980, StrPrice: "0.04980", Amount: 0.00000500, StrAmount: "0.00000500"},
{Price: 0.04975, StrPrice: "0.04975", Amount: 0.00000500, StrAmount: "0.00000500"},
{Price: 0.04970, StrPrice: "0.04970", Amount: 0.00000500, StrAmount: "0.00000500"},
{Price: 0.04965, StrPrice: "0.04965", Amount: 0.00000500, StrAmount: "0.00000500"},
{Price: 0.04960, StrPrice: "0.04960", Amount: 0.00000500, StrAmount: "0.00000500"},
{Price: 0.04955, StrPrice: "0.04955", Amount: 0.00000500, StrAmount: "0.00000500"},
{Price: 0.04950, StrPrice: "0.04950", Amount: 0.00000500, StrAmount: "0.00000500"},
},
}
const krakenAPIDocChecksum = 974947235
func TestChecksumCalculation(t *testing.T) {
t.Parallel()
expected := "5005"
if v := trim("0.05005"); v != expected {
t.Errorf("expected %s but received %s", expected, v)
}
expected = "500"
if v := trim("0.00000500"); v != expected {
t.Errorf("expected %s but received %s", expected, v)
}
err := validateCRC32(&testOb, krakenAPIDocChecksum)
if err != nil {
t.Error(err)
}
}
func TestGetCharts(t *testing.T) {
t.Parallel()
resp, err := k.GetFuturesCharts(context.Background(), "1d", "spot", futuresTestPair, time.Time{}, time.Time{})
require.NoError(t, err)
require.NotEmpty(t, resp.Candles)
end := time.UnixMilli(resp.Candles[0].Time)
_, err = k.GetFuturesCharts(context.Background(), "1d", "spot", futuresTestPair, end.Add(-time.Hour*24*7), end)
require.NoError(t, err)
}
func TestGetFuturesTrades(t *testing.T) {
t.Parallel()
_, err := k.GetFuturesTrades(context.Background(), futuresTestPair, time.Time{}, time.Time{})
assert.NoError(t, err, "GetFuturesTrades should not error")
_, err = k.GetFuturesTrades(context.Background(), futuresTestPair, time.Now().Add(-time.Hour), time.Now())
assert.NoError(t, err, "GetFuturesTrades should not error")
}
var websocketXDGUSDOrderbookUpdates = []string{
`[2304,{"as":[["0.074602700","278.39626342","1690246067.832139"],["0.074611000","555.65134028","1690246086.243668"],["0.074613300","524.87121572","1690245901.574881"],["0.074624600","77.57180740","1690246060.668500"],["0.074632500","620.64648404","1690246010.904883"],["0.074698400","409.57419037","1690246041.269821"],["0.074700000","61067.71115772","1690246089.485595"],["0.074723200","4394.01869240","1690246087.557913"],["0.074725200","4229.57885125","1690246082.911452"],["0.074738400","212.25501214","1690246089.421559"]],"bs":[["0.074597400","53591.43163675","1690246089.451762"],["0.074596700","33594.18269213","1690246089.514152"],["0.074596600","53598.60351469","1690246089.340781"],["0.074594800","5358.57247081","1690246089.347962"],["0.074594200","30168.21074680","1690246089.345112"],["0.074590900","7089.69894583","1690246088.212880"],["0.074586700","46925.20182082","1690246089.074618"],["0.074577200","5500.00000000","1690246087.568856"],["0.074569600","8132.49888631","1690246086.841219"],["0.074562900","8413.11098009","1690246087.024863"]]},"book-10","XDG/USD"]`,
`[2304,{"a":[["0.074700000","0.00000000","1690246089.516119"],["0.074738500","125000.00000000","1690246063.352141","r"]],"c":"2219685759"},"book-10","XDG/USD"]`,
`[2304,{"a":[["0.074678800","33476.70673703","1690246089.570183"]],"c":"1897176819"},"book-10","XDG/USD"]`,
`[2304,{"b":[["0.074562900","0.00000000","1690246089.570206"],["0.074559600","4000.00000000","1690246086.478591","r"]],"c":"2498018751"},"book-10","XDG/USD"]`,
`[2304,{"b":[["0.074577300","125000.00000000","1690246089.577140"]],"c":"155006629"},"book-10","XDG/USD"]`,
`[2304,{"a":[["0.074678800","0.00000000","1690246089.584498"],["0.074738500","125000.00000000","1690246063.352141","r"]],"c":"3703147735"},"book-10","XDG/USD"]`,
`[2304,{"b":[["0.074597500","10000.00000000","1690246089.602477"]],"c":"2989534775"},"book-10","XDG/USD"]`,
`[2304,{"a":[["0.074738500","0.00000000","1690246089.608769"],["0.074750800","51369.02100000","1690246089.495500","r"]],"c":"1842075082"},"book-10","XDG/USD"]`,
`[2304,{"b":[["0.074583500","8413.11098009","1690246089.612144"]],"c":"710274752"},"book-10","XDG/USD"]`,
`[2304,{"b":[["0.074578500","9966.55841398","1690246089.634739"]],"c":"1646135532"},"book-10","XDG/USD"]`,
`[2304,{"a":[["0.074738400","0.00000000","1690246089.638648"],["0.074751500","80499.09450000","1690246086.679402","r"]],"c":"2509689626"},"book-10","XDG/USD"]`,
`[2304,{"a":[["0.074750700","290.96851266","1690246089.638754"]],"c":"3981738175"},"book-10","XDG/USD"]`,
`[2304,{"a":[["0.074720000","61067.71115772","1690246089.662102"]],"c":"1591820326"},"book-10","XDG/USD"]`,
`[2304,{"a":[["0.074602700","0.00000000","1690246089.670911"],["0.074750800","51369.02100000","1690246089.495500","r"]],"c":"3838272404"},"book-10","XDG/USD"]`,
`[2304,{"a":[["0.074611000","0.00000000","1690246089.680343"],["0.074758500","159144.39750000","1690246035.158327","r"]],"c":"4241552383"},"book-10","XDG/USD"] `,
}
var websocketLUNAEUROrderbookUpdates = []string{
`[9536,{"as":[["0.000074650000","147354.32016076","1690249755.076929"],["0.000074710000","5084881.40000000","1690250711.359411"],["0.000074760000","9700502.70476704","1690250743.279490"],["0.000074990000","2933380.23886300","1690249596.627969"],["0.000075000000","433333.33333333","1690245575.626780"],["0.000075020000","152914.84493416","1690243661.232520"],["0.000075070000","146529.90542161","1690249048.358424"],["0.000075250000","737072.85720004","1690211553.549248"],["0.000075400000","670061.64567140","1690250769.261196"],["0.000075460000","980226.63603417","1690250769.627523"]],"bs":[["0.000074590000","71029.87806720","1690250763.012724"],["0.000074580000","15935576.86404000","1690250763.012710"],["0.000074520000","33758611.79634000","1690250718.290955"],["0.000074350000","3156650.58590277","1690250766.499648"],["0.000074340000","301727260.79999999","1690250766.490238"],["0.000074320000","64611496.53837000","1690250742.680258"],["0.000074310000","104228596.60000000","1690250744.679121"],["0.000074300000","40366046.10582000","1690250762.685914"],["0.000074200000","3690216.57320475","1690250645.311465"],["0.000074060000","1337170.52532521","1690250742.012527"]]},"book-10","LUNA/EUR"]`,
`[9536,{"b":[["0.000074060000","0.00000000","1690250770.616604"],["0.000074050000","16742421.17790510","1690250710.867730","r"]],"c":"418307145"},"book-10","LUNA/EUR"]`,
}
var websocketGSTEUROrderbookUpdates = []string{
`[8912,{"as":[["0.01300","850.00000000","1690230914.230506"],["0.01400","323483.99590510","1690256356.615823"],["0.01500","100287.34442717","1690219133.193345"],["0.01600","67995.78441017","1690118389.451216"],["0.01700","41776.38397740","1689676303.381189"],["0.01800","11785.76177777","1688631951.812452"],["0.01900","23700.00000000","1686935422.319042"],["0.02000","3941.17000000","1689415829.176481"],["0.02100","16598.69173066","1689420942.541943"],["0.02200","17572.51572836","1689851425.907427"]],"bs":[["0.01200","14220.66466572","1690256540.842831"],["0.01100","160223.61546438","1690256401.072463"],["0.01000","63083.48958963","1690256604.037673"],["0.00900","6750.00000000","1690252470.633938"],["0.00800","213059.49706376","1690256360.386301"],["0.00700","1000.00000000","1689869458.464975"],["0.00600","4000.00000000","1690221333.528698"],["0.00100","245000.00000000","1690051368.753455"]]},"book-10","GST/EUR"]`,
`[8912,{"b":[["0.01000","60583.48958963","1690256620.206768"],["0.01000","63083.48958963","1690256620.206783"]],"c":"69619317"},"book-10","GST/EUR"]`,
}
func TestWsOrderbookMax10Depth(t *testing.T) {
t.Parallel()
k := new(Kraken) //nolint:govet // Intentional shadow to avoid future copy/paste mistakes
require.NoError(t, testexch.Setup(k), "Setup Instance must not error")
pairs := currency.Pairs{
currency.NewPairWithDelimiter("XDG", "USD", "/"),
currency.NewPairWithDelimiter("LUNA", "EUR", "/"),
currency.NewPairWithDelimiter("GST", "EUR", "/"),
}
for _, p := range pairs {
err := k.Websocket.AddSuccessfulSubscriptions(k.Websocket.Conn, &subscription.Subscription{
Channel: subscription.OrderbookChannel,
Pairs: currency.Pairs{p},
Asset: asset.Spot,
Levels: 10,
})
require.NoError(t, err, "AddSuccessfulSubscriptions must not error")
}
for x := range websocketXDGUSDOrderbookUpdates {
err := k.wsHandleData([]byte(websocketXDGUSDOrderbookUpdates[x]))
require.NoError(t, err, "wsHandleData should not error")
}
for x := range websocketLUNAEUROrderbookUpdates {
err := k.wsHandleData([]byte(websocketLUNAEUROrderbookUpdates[x]))
// TODO: Known issue with LUNA pairs and big number float precision
// storage and checksum calc. Might need to store raw strings as fields
// in the orderbook.Tranche struct.
// Required checksum: 7465000014735432016076747100005084881400000007476000097005027047670474990000293338023886300750000004333333333333375020000152914844934167507000014652990542161752500007370728572000475400000670061645671407546000098022663603417745900007102987806720745800001593557686404000745200003375861179634000743500003156650585902777434000030172726079999999743200006461149653837000743100001042285966000000074300000403660461058200074200000369021657320475740500001674242117790510
if x != len(websocketLUNAEUROrderbookUpdates)-1 {
require.NoError(t, err, "wsHandleData should not error")
}
}
// This has less than 10 bids and still needs a checksum calc.
for x := range websocketGSTEUROrderbookUpdates {
err := k.wsHandleData([]byte(websocketGSTEUROrderbookUpdates[x]))
require.NoError(t, err, "wsHandleData should not error")
}
}
func TestGetFuturesContractDetails(t *testing.T) {
t.Parallel()
_, err := k.GetFuturesContractDetails(context.Background(), asset.Spot)
if !errors.Is(err, futures.ErrNotFuturesAsset) {
t.Error(err)
}
_, err = k.GetFuturesContractDetails(context.Background(), asset.USDTMarginedFutures)
if !errors.Is(err, asset.ErrNotSupported) {
t.Error(err)
}
_, err = k.GetFuturesContractDetails(context.Background(), asset.Futures)
assert.NoError(t, err, "GetFuturesContractDetails should not error")
}
func TestGetLatestFundingRates(t *testing.T) {
t.Parallel()
_, err := k.GetLatestFundingRates(context.Background(), &fundingrate.LatestRateRequest{
Asset: asset.USDTMarginedFutures,
Pair: currency.NewPair(currency.BTC, currency.USD),
IncludePredictedRate: true,
})
assert.ErrorIs(t, err, asset.ErrNotSupported, "GetLatestFundingRates should error")
_, err = k.GetLatestFundingRates(context.Background(), &fundingrate.LatestRateRequest{
Asset: asset.Futures,
})
assert.NoError(t, err, "GetLatestFundingRates should not error")
err = k.CurrencyPairs.EnablePair(asset.Futures, futuresTestPair)
assert.True(t, err == nil || errors.Is(err, currency.ErrPairAlreadyEnabled), "EnablePair should not error")
_, err = k.GetLatestFundingRates(context.Background(), &fundingrate.LatestRateRequest{
Asset: asset.Futures,
Pair: futuresTestPair,
IncludePredictedRate: true,
})
assert.NoError(t, err, "GetLatestFundingRates should not error")
}
func TestIsPerpetualFutureCurrency(t *testing.T) {
t.Parallel()
is, err := k.IsPerpetualFutureCurrency(asset.Binary, currency.NewPair(currency.BTC, currency.USDT))
assert.NoError(t, err)
assert.False(t, is, "IsPerpetualFutureCurrency should return false for a binary asset")
is, err = k.IsPerpetualFutureCurrency(asset.Futures, currency.NewPair(currency.BTC, currency.USDT))
assert.NoError(t, err)
assert.False(t, is, "IsPerpetualFutureCurrency should return false for a non-perpetual future")
is, err = k.IsPerpetualFutureCurrency(asset.Futures, futuresTestPair)
assert.NoError(t, err)
assert.True(t, is, "IsPerpetualFutureCurrency should return true for a perpetual future")
}
func TestGetOpenInterest(t *testing.T) {
t.Parallel()
k := new(Kraken) //nolint:govet // Intentional shadow to avoid future copy/paste mistakes
require.NoError(t, testexch.Setup(k), "Test instance Setup must not error")
_, err := k.GetOpenInterest(context.Background(), key.PairAsset{
Base: currency.ETH.Item,
Quote: currency.USDT.Item,
Asset: asset.USDTMarginedFutures,
})
assert.ErrorIs(t, err, asset.ErrNotSupported)
cp1 := currency.NewPair(currency.PF, currency.NewCode("XBTUSD"))
cp2 := currency.NewPair(currency.PF, currency.NewCode("ETHUSD"))
sharedtestvalues.SetupCurrencyPairsForExchangeAsset(t, k, asset.Futures, cp1, cp2)
resp, err := k.GetOpenInterest(context.Background(), key.PairAsset{
Base: cp1.Base.Item,
Quote: cp1.Quote.Item,
Asset: asset.Futures,
})
assert.NoError(t, err)
assert.NotEmpty(t, resp)
resp, err = k.GetOpenInterest(context.Background(),
key.PairAsset{
Base: cp1.Base.Item,
Quote: cp1.Quote.Item,
Asset: asset.Futures,
},
key.PairAsset{
Base: cp2.Base.Item,
Quote: cp2.Quote.Item,
Asset: asset.Futures,
})
assert.NoError(t, err)
assert.NotEmpty(t, resp)
resp, err = k.GetOpenInterest(context.Background())
assert.NoError(t, err)
assert.NotEmpty(t, resp)
}
// curryWsMockUpgrader handles Kraken specific http auth token responses prior to handling off to standard Websocket upgrader
func curryWsMockUpgrader(tb testing.TB, h mockws.WsMockFunc) http.HandlerFunc {
tb.Helper()
return func(w http.ResponseWriter, r *http.Request) {
if strings.Contains(r.URL.Path, "GetWebSocketsToken") {
_, err := w.Write([]byte(`{"result":{"token":"mockAuth"}}`))
assert.NoError(tb, err, "Write should not error")
return
}
mockws.WsMockUpgrader(tb, w, r, h)
}
}
func TestGetCurrencyTradeURL(t *testing.T) {
t.Parallel()
testexch.UpdatePairsOnce(t, k)
for _, a := range k.GetAssetTypes(false) {
pairs, err := k.CurrencyPairs.GetPairs(a, false)
if len(pairs) == 0 {
continue
}
require.NoError(t, err, "cannot get pairs for %s", a)
resp, err := k.GetCurrencyTradeURL(context.Background(), a, pairs[0])
if a != asset.Spot && a != asset.Futures {
assert.ErrorIs(t, err, asset.ErrNotSupported)
continue
}
require.NoError(t, err)
assert.NotEmpty(t, resp)
}
}
func TestErrorResponse(t *testing.T) {
var g genericRESTResponse
tests := []struct {
name string
jsonStr string
expectError bool
errorMsg string
warningMsg string
requiresReset bool
}{
{
name: "No errors or warnings",
jsonStr: `{"error":[],"result":{"unixtime":1721884425,"rfc1123":"Thu, 25 Jul 24 05:13:45 +0000"}}`,
},
{
name: "Invalid error type int",
jsonStr: `{"error":[69420],"result":{}}`,
expectError: true,
errorMsg: "unable to convert 69420 to string",
},
{
name: "Unhandled error type float64",
jsonStr: `{"error":124,"result":{}}`,
expectError: true,
errorMsg: "unhandled error response type float64",
},
{
name: "Known error string",
jsonStr: `{"error":["EQuery:Unknown asset pair"],"result":{}}`,
errorMsg: "EQuery:Unknown asset pair",
},
{
name: "Known error string (single)",
jsonStr: `{"error":"EService:Unavailable","result":{}}`,
errorMsg: "EService:Unavailable",
},
{
name: "Warning string in array",
jsonStr: `{"error":["WGeneral:Danger"],"result":{}}`,
warningMsg: "WGeneral:Danger",
requiresReset: true,
},
{
name: "Warning string",
jsonStr: `{"error":"WGeneral:Unknown warning","result":{}}`,
warningMsg: "WGeneral:Unknown warning",
requiresReset: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if tt.requiresReset {
g = genericRESTResponse{}
}
err := json.Unmarshal([]byte(tt.jsonStr), &g)
if tt.expectError {
require.ErrorContains(t, err, tt.errorMsg, "Unmarshal should error")
} else {
require.NoError(t, err)
if tt.errorMsg != "" {
assert.ErrorContains(t, g.Error.Errors(), tt.errorMsg, "Errors should contain %s", tt.errorMsg)
} else {
assert.NoError(t, g.Error.Errors(), "Errors should not error")
}
if tt.warningMsg != "" {
assert.Contains(t, g.Error.Warnings(), tt.warningMsg, "Warnings should contain %s", tt.warningMsg)
} else {
assert.Empty(t, g.Error.Warnings(), "Warnings should be empty")
}
}
})
}
}
func TestGetFuturesErr(t *testing.T) {
t.Parallel()
assert.ErrorContains(t, getFuturesErr(json.RawMessage(`unparsable rubbish`)), "invalid character", "Bad JSON should error correctly")
assert.NoError(t, getFuturesErr(json.RawMessage(`{"candles":[]}`)), "JSON with no Result should not error")
assert.NoError(t, getFuturesErr(json.RawMessage(`{"Result":"4 goats"}`)), "JSON with non-error Result should not error")
assert.ErrorIs(t, getFuturesErr(json.RawMessage(`{"Result":"error"}`)), common.ErrUnknownError, "JSON with error Result should error correctly")
assert.ErrorContains(t, getFuturesErr(json.RawMessage(`{"Result":"error", "error": "1 goat"}`)), "1 goat", "JSON with an error should error correctly")
err := getFuturesErr(json.RawMessage(`{"Result":"error", "errors": ["2 goats", "3 goats"]}`))
assert.ErrorContains(t, err, "2 goat", "JSON with errors should error correctly")
assert.ErrorContains(t, err, "3 goat", "JSON with errors should error correctly")
err = getFuturesErr(json.RawMessage(`{"Result":"error", "error": "too many goats", "errors": ["2 goats", "3 goats"]}`))
assert.ErrorContains(t, err, "2 goat", "JSON with both error and errors should error correctly")
assert.ErrorContains(t, err, "3 goat", "JSON with both error and errors should error correctly")
assert.ErrorContains(t, err, "too many goat", "JSON both error and with errors should error correctly")
}
func TestEnforceStandardChannelNames(t *testing.T) {
for _, n := range []string{
krakenWsSpread, krakenWsTicker, subscription.TickerChannel, subscription.OrderbookChannel, subscription.CandlesChannel,
subscription.AllTradesChannel, subscription.MyTradesChannel, subscription.MyOrdersChannel,
} {
assert.NoError(t, enforceStandardChannelNames(&subscription.Subscription{Channel: n}), "Standard channel names and bespoke names should not error")
}
for _, n := range []string{krakenWsOrderbook, krakenWsOHLC, krakenWsTrade, krakenWsOwnTrades, krakenWsOpenOrders, krakenWsOrderbook + "-5"} {
err := enforceStandardChannelNames(&subscription.Subscription{Channel: n})
assert.ErrorIsf(t, err, subscription.ErrUseConstChannelName, "Private channel names should not be allowed for %s", n)
}
}