mirror of
https://github.com/d0zingcat/gocryptotrader.git
synced 2026-06-05 23:16:53 +00:00
accounts: Move to instance methods, fix races and isolate tests (#1923)
* Bybit: Fix race in TestUpdateAccountInfo and TestWSHandleData * DriveBy rename TestWSHandleData * This doesn't address running with -race=2+ due to the singleton * Accounts: Add account.GetService() * exchange: Assertify TestSetupDefaults * Exchanges: Add account.Service override for testing * Exchanges: Remove duplicate IsWebsocketEnabled test from TestSetupDefaults * Dispatch: Replace nil checks with NilGuard * Engine: Remove deprecated printAccountHoldingsChangeSummary * Dispatcher: Add EnsureRunning method * Accounts: Move singleton accounts service to exchange Accounts * Move singleton accounts service to exchange Accounts This maintains the concept of a global store, whilst allowing exchanges to override it when needed, particularly for testing. APIServer: * Remove getAllActiveAccounts from apiserver Deprecated apiserver only thing using this, so remove it instead of updating it * Update comment for UpdateAccountBalances everywhere * Docs: Add punctuation to function comments * Bybit: Coverage for wsProcessWalletPushData Save
This commit is contained in:
@@ -2,6 +2,9 @@ package gateio
|
||||
|
||||
import (
|
||||
"context"
|
||||
"maps"
|
||||
"slices"
|
||||
"strconv"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
@@ -9,8 +12,8 @@ import (
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/thrasher-corp/gocryptotrader/currency"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchange/accounts"
|
||||
exchange "github.com/thrasher-corp/gocryptotrader/exchanges"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/account"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
|
||||
)
|
||||
|
||||
@@ -42,17 +45,17 @@ type websocketBalancesTest struct {
|
||||
input []byte
|
||||
err error
|
||||
deployCreds bool
|
||||
expected []account.Change
|
||||
expected accounts.SubAccounts
|
||||
}
|
||||
|
||||
func TestProcessSpotBalances(t *testing.T) {
|
||||
func TestProcessSpotBalances(t *testing.T) { //nolint:tparallel // Sequential tests, do not use t.Parallel(); Some timestamps are deliberately identical from trading activity
|
||||
t.Parallel()
|
||||
e := new(Exchange) //nolint:govet // Intentional shadow
|
||||
e.SetDefaults()
|
||||
e.Name = "ProcessSpotBalancesTest"
|
||||
e.Accounts = accounts.MustNewAccounts(e)
|
||||
|
||||
// Sequential tests, do not use t.Run(); Some timestamps are deliberately identical from trading activity
|
||||
for _, tc := range []websocketBalancesTest{
|
||||
for i, tc := range []websocketBalancesTest{
|
||||
{
|
||||
input: []byte(`[{"timestamp":"1755718222"}]`),
|
||||
err: exchange.ErrCredentialsAreEmpty,
|
||||
@@ -60,17 +63,19 @@ func TestProcessSpotBalances(t *testing.T) {
|
||||
{
|
||||
deployCreds: true,
|
||||
input: []byte(`[{"timestamp":"1755718222","timestamp_ms":"1755718222394","user":"12870774","currency":"USDT","change":"0","total":"3087.01142272991036062136","available":"3081.68642272991036062136","freeze":"5.325","freeze_change":"5.32500000000000000000","change_type":"order-create"}]`),
|
||||
expected: []account.Change{
|
||||
expected: accounts.SubAccounts{
|
||||
{
|
||||
Account: "12870774",
|
||||
ID: "12870774",
|
||||
AssetType: asset.Spot,
|
||||
Balance: &account.Balance{
|
||||
Currency: currency.USDT,
|
||||
Total: 3087.01142272991036062136,
|
||||
Free: 3081.68642272991036062136,
|
||||
Hold: 5.325,
|
||||
AvailableWithoutBorrow: 3081.68642272991036062136,
|
||||
UpdatedAt: time.UnixMilli(1755718222394),
|
||||
Balances: accounts.CurrencyBalances{
|
||||
currency.USDT: accounts.Balance{
|
||||
Currency: currency.USDT,
|
||||
Total: 3087.01142272991036062136,
|
||||
Free: 3081.68642272991036062136,
|
||||
Hold: 5.325,
|
||||
AvailableWithoutBorrow: 3081.68642272991036062136,
|
||||
UpdatedAt: time.UnixMilli(1755718222394),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -78,44 +83,51 @@ func TestProcessSpotBalances(t *testing.T) {
|
||||
{
|
||||
deployCreds: true,
|
||||
input: []byte(`[{"timestamp":"1755718222","timestamp_ms":"1755718222394","user":"12870774","currency":"USDT","change":"-3.99375000000000000000","total":"3083.01767272991036062136","available":"3081.68642272991036062136","freeze":"1.33125","freeze_change":"-3.99375000000000000000","change_type":"order-match"}]`),
|
||||
expected: []account.Change{
|
||||
expected: accounts.SubAccounts{
|
||||
{
|
||||
Account: "12870774",
|
||||
ID: "12870774",
|
||||
AssetType: asset.Spot,
|
||||
Balance: &account.Balance{
|
||||
Currency: currency.USDT,
|
||||
Total: 3083.01767272991036062136,
|
||||
Free: 3081.68642272991036062136,
|
||||
Hold: 1.33125,
|
||||
AvailableWithoutBorrow: 3081.68642272991036062136,
|
||||
UpdatedAt: time.UnixMilli(1755718222394),
|
||||
Balances: accounts.CurrencyBalances{
|
||||
currency.USDT: accounts.Balance{
|
||||
Currency: currency.USDT,
|
||||
Total: 3083.01767272991036062136,
|
||||
Free: 3081.68642272991036062136,
|
||||
Hold: 1.33125,
|
||||
AvailableWithoutBorrow: 3081.68642272991036062136,
|
||||
UpdatedAt: time.UnixMilli(1755718222394),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
} {
|
||||
ctx := t.Context()
|
||||
if tc.deployCreds {
|
||||
ctx = account.DeployCredentialsToContext(ctx, &account.Credentials{Key: "test", Secret: "test"})
|
||||
}
|
||||
err := e.processSpotBalances(ctx, tc.input)
|
||||
if tc.err != nil {
|
||||
require.ErrorIs(t, err, tc.err)
|
||||
continue
|
||||
}
|
||||
require.NoError(t, err, "processSpotBalances must not error")
|
||||
checkAccountChange(ctx, t, e, &tc)
|
||||
t.Run(strconv.Itoa(i), func(t *testing.T) {
|
||||
// Sequential tests, do not use t.Parallel(); Some timestamps are deliberately identical from trading activity
|
||||
ctx := t.Context()
|
||||
if tc.deployCreds {
|
||||
ctx = accounts.DeployCredentialsToContext(ctx, &accounts.Credentials{Key: "test", Secret: "test"})
|
||||
}
|
||||
err := e.processSpotBalances(ctx, tc.input)
|
||||
if tc.err != nil {
|
||||
require.ErrorIs(t, err, tc.err)
|
||||
} else {
|
||||
require.NoError(t, err, "processSpotBalances must not error")
|
||||
checkAccountChange(ctx, t, e, &tc)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestProcessBalancePushData(t *testing.T) {
|
||||
func TestProcessBalancePushData(t *testing.T) { //nolint:tparallel // Sequential tests, do not use t.Parallel(); Some timestamps are deliberately identical from trading activity
|
||||
t.Parallel()
|
||||
e := new(Exchange) //nolint:govet // Intentional shadow
|
||||
e.SetDefaults()
|
||||
e.Name = "ProcessFuturesBalancesTest"
|
||||
e.Accounts = accounts.MustNewAccounts(e)
|
||||
|
||||
// Sequential tests, do not use t.Run(); Some timestamps are deliberately identical from trading activity
|
||||
for _, tc := range []websocketBalancesTest{
|
||||
usdtLower := currency.USDT.Lower()
|
||||
|
||||
for i, tc := range []websocketBalancesTest{
|
||||
{
|
||||
input: []byte(`[{"timestamp":"1755718222"}]`),
|
||||
err: exchange.ErrCredentialsAreEmpty,
|
||||
@@ -123,16 +135,18 @@ func TestProcessBalancePushData(t *testing.T) {
|
||||
{
|
||||
deployCreds: true,
|
||||
input: []byte(`[{"balance":2214.191673190433,"change":-0.0025776,"currency":"usdt","text":"TCOM_USDT:263179103241933596","time":1755738515,"time_ms":1755738515671,"type":"fee","user":"12870774"}]`),
|
||||
expected: []account.Change{
|
||||
expected: accounts.SubAccounts{
|
||||
{
|
||||
Account: "12870774",
|
||||
ID: "12870774",
|
||||
AssetType: asset.USDTMarginedFutures,
|
||||
Balance: &account.Balance{
|
||||
Currency: currency.USDT,
|
||||
Total: 2214.191673190433,
|
||||
Free: 2214.191673190433,
|
||||
AvailableWithoutBorrow: 2214.191673190433,
|
||||
UpdatedAt: time.UnixMilli(1755738515671),
|
||||
Balances: accounts.CurrencyBalances{
|
||||
usdtLower: accounts.Balance{
|
||||
Currency: usdtLower,
|
||||
Total: 2214.191673190433,
|
||||
Free: 2214.191673190433,
|
||||
AvailableWithoutBorrow: 2214.191673190433,
|
||||
UpdatedAt: time.UnixMilli(1755738515671),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -140,33 +154,37 @@ func TestProcessBalancePushData(t *testing.T) {
|
||||
{
|
||||
deployCreds: true,
|
||||
input: []byte(`[{"balance":2214.189114310433,"change":-0.00255888,"currency":"usdt","text":"TCOM_USDT:263179103241933644","time":1755738516,"time_ms":1755738516430,"type":"fee","user":"12870774"}]`),
|
||||
expected: []account.Change{
|
||||
expected: accounts.SubAccounts{
|
||||
{
|
||||
Account: "12870774",
|
||||
ID: "12870774",
|
||||
AssetType: asset.USDTMarginedFutures,
|
||||
Balance: &account.Balance{
|
||||
Currency: currency.USDT,
|
||||
Total: 2214.189114310433,
|
||||
Free: 2214.189114310433,
|
||||
AvailableWithoutBorrow: 2214.189114310433,
|
||||
UpdatedAt: time.UnixMilli(1755738516430),
|
||||
Balances: accounts.CurrencyBalances{
|
||||
usdtLower: accounts.Balance{
|
||||
Currency: usdtLower,
|
||||
Total: 2214.189114310433,
|
||||
Free: 2214.189114310433,
|
||||
AvailableWithoutBorrow: 2214.189114310433,
|
||||
UpdatedAt: time.UnixMilli(1755738516430),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
} {
|
||||
ctx := t.Context()
|
||||
if tc.deployCreds {
|
||||
ctx = account.DeployCredentialsToContext(ctx, &account.Credentials{Key: "test", Secret: "test"})
|
||||
}
|
||||
err := e.processBalancePushData(ctx, tc.input, asset.USDTMarginedFutures)
|
||||
if tc.err != nil {
|
||||
require.ErrorIs(t, err, tc.err)
|
||||
continue
|
||||
}
|
||||
require.NoError(t, err, "processBalancePushData must not error")
|
||||
require.Len(t, e.Websocket.DataHandler, 1)
|
||||
checkAccountChange(ctx, t, e, &tc)
|
||||
t.Run(strconv.Itoa(i), func(t *testing.T) {
|
||||
// Sequential tests, do not use t.Parallel(); Some timestamps are deliberately identical from trading activity
|
||||
ctx := t.Context()
|
||||
if tc.deployCreds {
|
||||
ctx = accounts.DeployCredentialsToContext(ctx, &accounts.Credentials{Key: "test", Secret: "test"})
|
||||
}
|
||||
err := e.processBalancePushData(ctx, tc.input, asset.USDTMarginedFutures)
|
||||
if tc.err != nil {
|
||||
require.ErrorIs(t, err, tc.err)
|
||||
} else {
|
||||
require.NoError(t, err, "processBalancePushData must not error")
|
||||
checkAccountChange(ctx, t, e, &tc)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -175,25 +193,19 @@ func checkAccountChange(ctx context.Context, t *testing.T, exch *Exchange, tc *w
|
||||
|
||||
require.Len(t, exch.Websocket.DataHandler, 1)
|
||||
payload := <-exch.Websocket.DataHandler
|
||||
received, ok := payload.([]account.Change)
|
||||
received, ok := payload.(accounts.SubAccounts)
|
||||
require.Truef(t, ok, "Expected account changes, got %T", payload)
|
||||
|
||||
require.Lenf(t, received, len(tc.expected), "Expected %d changes, got %d", len(tc.expected), len(received))
|
||||
for i, change := range received {
|
||||
assert.Equal(t, tc.expected[i].Account, change.Account, "account should equal")
|
||||
assert.Equal(t, tc.expected[i].AssetType, change.AssetType, "asset type should equal")
|
||||
assert.True(t, tc.expected[i].Balance.Currency.Equal(change.Balance.Currency), "currency should equal")
|
||||
assert.Equal(t, tc.expected[i].Balance.Total, change.Balance.Total, "total should equal")
|
||||
assert.Equal(t, tc.expected[i].Balance.Hold, change.Balance.Hold, "hold should equal")
|
||||
assert.Equal(t, tc.expected[i].Balance.Free, change.Balance.Free, "free should equal")
|
||||
assert.Equal(t, tc.expected[i].Balance.AvailableWithoutBorrow, change.Balance.AvailableWithoutBorrow, "available without borrow should equal")
|
||||
assert.Equal(t, tc.expected[i].Balance.Borrowed, change.Balance.Borrowed, "borrowed should equal")
|
||||
assert.Equal(t, tc.expected[i].Balance.UpdatedAt, change.Balance.UpdatedAt, "updated at should equal")
|
||||
require.Equal(t, tc.expected, received)
|
||||
|
||||
creds, err := exch.GetCredentials(ctx)
|
||||
require.NoError(t, err, "GetCredentials must not error")
|
||||
stored, err := account.GetBalance(exch.Name, tc.expected[i].Account, creds, tc.expected[i].AssetType, tc.expected[i].Balance.Currency)
|
||||
creds, err := exch.GetCredentials(ctx)
|
||||
require.NoError(t, err, "GetCredentials must not error")
|
||||
|
||||
for _, change := range received {
|
||||
bal := slices.Collect(maps.Values(change.Balances))[0]
|
||||
stored, err := exch.Accounts.GetBalance(change.ID, creds, change.AssetType, bal.Currency)
|
||||
require.NoError(t, err, "GetBalance must not error")
|
||||
assert.Equal(t, tc.expected[i].Balance.Free, stored.GetFree(), "free balance should equal with accounts stored value")
|
||||
assert.Equal(t, bal.Free, stored.Free, "free balance should equal with accounts stored value")
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user