Skip to content

Commit

Permalink
Slight improvements
Browse files Browse the repository at this point in the history
  • Loading branch information
cranktakular committed Apr 22, 2024
1 parent 55596d8 commit 343052b
Show file tree
Hide file tree
Showing 6 changed files with 173 additions and 127 deletions.
2 changes: 1 addition & 1 deletion exchanges/coinbasepro/coinbasepro.go
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,7 @@ var (
errPairEmpty = errors.New("pair cannot be empty")
errStringConvert = errors.New("unable to convert into string value")
errFloatConvert = errors.New("unable to convert into float64 value")
errConvertGen = errors.New("unable to convert value")
errNoCredsUser = errors.New("no credentials when attempting to subscribe to authenticated channel user")
)

// GetAllAccounts returns information on all trading accounts associated with the API key
Expand Down
211 changes: 117 additions & 94 deletions exchanges/coinbasepro/coinbasepro_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import (
"github.com/gofrs/uuid"
"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/config"
"github.com/thrasher-corp/gocryptotrader/currency"
Expand Down Expand Up @@ -49,6 +50,8 @@ var (
// Constants used within tests
const (
testAddress = "fake address"
testAmount = 1e-08
testPrice = 1e+09

skipPayMethodNotFound = "no payment methods found, skipping"
skipInsufSuitableAccs = "insufficient suitable accounts for test, skipping"
Expand All @@ -63,27 +66,24 @@ const (
errExpectedNonEmpty = "expected non-empty response"
errIDNotSet = "ID not set"
errx7f = "setting proxy address error parse \"\\x7f\": net/url: invalid control character in URL"
errPortfolioNameDuplicate = `CoinbasePro unsuccessful HTTP status code: 500 raw response: {"error":"INTERNAL","error_details":"the requested portfolio name already exists","message":"the requested portfolio name already exists"}, authenticated request failed`
errPortfolioNameDuplicate = `CoinbasePro unsuccessful HTTP status code: 409 raw response: {"error":"CONFLICT","error_details":"the requested portfolio name already exists","message":"the requested portfolio name already exists"}, authenticated request failed`
errPortTransferInsufFunds = `CoinbasePro unsuccessful HTTP status code: 429 raw response: {"error":"unknown","error_details":"[PORTFOLIO_ERROR_CODE_INSUFFICIENT_FUNDS] insufficient funds in source account","message":"[PORTFOLIO_ERROR_CODE_INSUFFICIENT_FUNDS] insufficient funds in source account"}, authenticated request failed`
errInvalidProductID = `CoinbasePro unsuccessful HTTP status code: 400 raw response: {"error":"INVALID_ARGUMENT","error_details":"valid product_id is required","message":"valid product_id is required"}, authenticated request failed`
errNoEndpointPathEdgeCase3 = "no endpoint path found for the given key: EdgeCase3URL"
errExpectedFeeRange = "expected fee range of %v and %v, received %v"
errUnrecognisedOrderType = `'' unrecognised order type`

expectedTimestamp = "1970-01-01 00:20:34 +0000 UTC"

testAmount = 1e-08
testPrice = 1e+09
)

func TestSetup(t *testing.T) {
func TestSetup2(t *testing.T) {
cfg, err := c.GetStandardConfig()
assert.NoError(t, err)
cfg.API.AuthenticatedSupport = true
cfg.API.Credentials.Key = apiKey
cfg.API.Credentials.Secret = apiSecret
cfg.Enabled = false
cfg.Enabled = true
// cfg.Enabled = false

Check failure on line 85 in exchanges/coinbasepro/coinbasepro_test.go

View workflow job for this annotation

GitHub Actions / lint

commentedOutCode: may want to remove commented-out code (gocritic)
// cfg.Enabled = true
cfg.ProxyAddress = string(rune(0x7f))
err = c.Setup(cfg)
if err.Error() != errx7f {
Expand All @@ -101,25 +101,9 @@ func TestMain(m *testing.M) {
log.Fatal("failed to set sandbox endpoint", err)
}
}
cfg := config.GetConfig()
err := cfg.LoadConfig("../../testdata/configtest.json", true)
if err != nil {
log.Fatal("load config error", err)
}
gdxConfig, err := cfg.GetExchangeConfig("CoinbasePro")
if err != nil {
log.Fatal("init error")
}
if apiKey != "" {
gdxConfig.API.Credentials.Key = apiKey
gdxConfig.API.Credentials.Secret = apiSecret
gdxConfig.API.AuthenticatedSupport = true
gdxConfig.API.AuthenticatedWebsocketSupport = true
}
c.Websocket = sharedtestvalues.NewTestWebsocket()
err = c.Setup(gdxConfig)
err := exchangeBaseHelper(c)
if err != nil {
log.Fatal("CoinbasePro setup error", err)
log.Fatal(err)
}
if apiKey != "" {
c.GetBase().API.AuthenticatedSupport = true
Expand All @@ -132,6 +116,33 @@ func TestMain(m *testing.M) {
os.Exit(m.Run())
}

func TestSetup(t *testing.T) {
cfg, err := c.GetStandardConfig()
assert.NoError(t, err)
exch := &CoinbasePro{}
exch.SetDefaults()
err = exchangeBaseHelper(exch)
require.NoError(t, err)
cfg.ProxyAddress = string(rune(0x7f))
err = exch.Setup(cfg)
assert.ErrorIs(t, err, exchange.ErrSettingProxyAddress)
}

func TestWsConnect(t *testing.T) {
exch := &CoinbasePro{}
exch.Websocket = sharedtestvalues.NewTestWebsocket()
sharedtestvalues.SkipTestIfCredentialsUnset(t, c)
err := exch.Websocket.Disable()
assert.ErrorIs(t, err, stream.ErrAlreadyDisabled)
err = exch.WsConnect()
assert.ErrorIs(t, err, stream.ErrWebsocketNotEnabled)
exch.SetDefaults()
err = exchangeBaseHelper(exch)
require.NoError(t, err)
err = exch.Websocket.Enable()
assert.NoError(t, err)
}

func TestGetAllAccounts(t *testing.T) {
t.Parallel()
sharedtestvalues.SkipTestIfCredentialsUnset(t, c)
Expand Down Expand Up @@ -475,59 +486,13 @@ func TestGetFuturesPositionByID(t *testing.T) {
assert.NoError(t, err)
}

func TestScheduleFuturesSweep(t *testing.T) {
t.Parallel()
sharedtestvalues.SkipTestIfCredentialsUnset(t, c, canManipulateRealOrders)
curSweeps, err := c.ListFuturesSweeps(context.Background())
assert.NoError(t, err)
preCancel := false
if len(curSweeps) > 0 {
for i := range curSweeps {
if curSweeps[i].Status == "PENDING" {
preCancel = true
}
}
}
if preCancel {
_, err = c.CancelPendingFuturesSweep(context.Background())
if err != nil {
t.Error(err)
}
}
_, err = c.ScheduleFuturesSweep(context.Background(), 0.001337)
assert.NoError(t, err)
}

func TestListFuturesSweeps(t *testing.T) {
t.Parallel()
sharedtestvalues.SkipTestIfCredentialsUnset(t, c)
_, err := c.ListFuturesSweeps(context.Background())
assert.NoError(t, err)
}

func TestCancelPendingFuturesSweep(t *testing.T) {
t.Parallel()
sharedtestvalues.SkipTestIfCredentialsUnset(t, c, canManipulateRealOrders)
curSweeps, err := c.ListFuturesSweeps(context.Background())
assert.NoError(t, err)
partialSkip := false
if len(curSweeps) > 0 {
for i := range curSweeps {
if curSweeps[i].Status == "PENDING" {
partialSkip = true
}
}
}
if !partialSkip {
_, err = c.ScheduleFuturesSweep(context.Background(), 0.001337)
if err != nil {
t.Error(err)
}
}
_, err = c.CancelPendingFuturesSweep(context.Background())
assert.NoError(t, err)
}

func TestAllocatePortfolio(t *testing.T) {
t.Parallel()
err := c.AllocatePortfolio(context.Background(), "", "", "", 0)
Expand Down Expand Up @@ -1573,9 +1538,64 @@ func TestFormatExchangeKlineInterval(t *testing.T) {
}
}

func TestStringToFloatPtr(t *testing.T) {
t.Parallel()
err := stringToFloatPtr(nil, "")
assert.ErrorIs(t, err, errPointerNil)
var fl float64
err = stringToFloatPtr(&fl, "")
assert.NoError(t, err)
err = stringToFloatPtr(&fl, "1.1")
assert.NoError(t, err)
}

func TestScheduleFuturesSweep(t *testing.T) {
sharedtestvalues.SkipTestIfCredentialsUnset(t, c, canManipulateRealOrders)
curSweeps, err := c.ListFuturesSweeps(context.Background())
assert.NoError(t, err)
preCancel := false
if len(curSweeps) > 0 {
for i := range curSweeps {
if curSweeps[i].Status == "PENDING" {
preCancel = true
}
}
}
if preCancel {
_, err = c.CancelPendingFuturesSweep(context.Background())
if err != nil {
t.Error(err)
}
}
_, err = c.ScheduleFuturesSweep(context.Background(), 0.001337)
assert.NoError(t, err)
}

func TestCancelPendingFuturesSweep(t *testing.T) {
sharedtestvalues.SkipTestIfCredentialsUnset(t, c, canManipulateRealOrders)
curSweeps, err := c.ListFuturesSweeps(context.Background())
assert.NoError(t, err)
partialSkip := false
if len(curSweeps) > 0 {
for i := range curSweeps {
if curSweeps[i].Status == "PENDING" {
partialSkip = true
}
}
}
if !partialSkip {
_, err = c.ScheduleFuturesSweep(context.Background(), 0.001337)
if err != nil {
t.Error(err)
}
}
_, err = c.CancelPendingFuturesSweep(context.Background())
assert.NoError(t, err)
}

// TestWsAuth dials websocket, sends login request.
func TestWsAuth(t *testing.T) {
if !c.Websocket.IsEnabled() && !c.API.AuthenticatedWebsocketSupport || !sharedtestvalues.AreAPICredentialsSet(c) {
if c.Websocket.IsEnabled() && !c.API.AuthenticatedWebsocketSupport || !sharedtestvalues.AreAPICredentialsSet(c) {
t.Skip(stream.ErrWebsocketNotEnabled.Error())
}
var dialer websocket.Dialer
Expand All @@ -1597,7 +1617,7 @@ func TestWsAuth(t *testing.T) {
timer := time.NewTimer(sharedtestvalues.WebsocketResponseDefaultTimeout)
select {
case badResponse := <-c.Websocket.DataHandler:
t.Error(badResponse)
assert.IsType(t, []order.Detail{}, badResponse)
case <-timer.C:
}
timer.Stop()
Expand Down Expand Up @@ -1625,27 +1645,6 @@ func TestStatusToStandardStatus(t *testing.T) {
}
}

func TestStringToFloatPtr(t *testing.T) {
t.Parallel()
err := stringToFloatPtr(nil, "")
assert.ErrorIs(t, err, errPointerNil)
var fl float64
err = stringToFloatPtr(&fl, "")
assert.NoError(t, err)
err = stringToFloatPtr(&fl, "1.1")
assert.NoError(t, err)
}

func TestWsConnect(t *testing.T) {
sharedtestvalues.SkipTestIfCredentialsUnset(t, c)
err := c.Websocket.Disable()
assert.ErrorIs(t, err, stream.ErrAlreadyDisabled)
err = c.WsConnect()
assert.ErrorIs(t, err, stream.ErrWebsocketNotEnabled)
err = c.Websocket.Enable()
assert.NoError(t, err)
}

func TestWsHandleData(t *testing.T) {
go func() {
for range c.Websocket.DataHandler {
Expand Down Expand Up @@ -1788,6 +1787,30 @@ func TestGetJWT(t *testing.T) {
}
}

func exchangeBaseHelper(c *CoinbasePro) error {
cfg := config.GetConfig()
err := cfg.LoadConfig("../../testdata/configtest.json", true)
if err != nil {
return err
}
gdxConfig, err := cfg.GetExchangeConfig("CoinbasePro")
if err != nil {
return err
}
if apiKey != "" {
gdxConfig.API.Credentials.Key = apiKey
gdxConfig.API.Credentials.Secret = apiSecret
gdxConfig.API.AuthenticatedSupport = true
gdxConfig.API.AuthenticatedWebsocketSupport = true
}
c.Websocket = sharedtestvalues.NewTestWebsocket()
err = c.Setup(gdxConfig)
if err != nil {
return err
}
return nil
}

func skipTestIfLowOnFunds(t *testing.T) {
t.Helper()
accounts, err := c.GetAllAccounts(context.Background(), 250, "")
Expand Down
44 changes: 29 additions & 15 deletions exchanges/coinbasepro/coinbasepro_websocket.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import (
"github.com/thrasher-corp/gocryptotrader/common"
"github.com/thrasher-corp/gocryptotrader/common/crypto"
"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/order"
"github.com/thrasher-corp/gocryptotrader/exchanges/orderbook"
Expand Down Expand Up @@ -221,7 +222,7 @@ func (c *CoinbasePro) wsHandleData(respRaw []byte, seqCount uint64) (string, err
if err != nil {
return warnString, err
}
sliToSend := []order.Detail{}
var sliToSend []order.Detail
for i := range wsUser {
for j := range wsUser[i].Orders {
var oType order.Type
Expand Down Expand Up @@ -432,31 +433,44 @@ func getTimestamp(rawData []byte) (time.Time, error) {

// sendRequest is a helper function which sends a websocket message to the Coinbase server
func (c *CoinbasePro) sendRequest(msgType, channel string, productIDs currency.Pairs) error {
authenticated := true
creds, err := c.GetCredentials(context.Background())
if err != nil {
return err
if errors.Is(err, exchange.ErrCredentialsAreEmpty) ||
errors.Is(err, exchange.ErrAuthenticationSupportNotEnabled) {
authenticated = false
if channel == "user" {
return errNoCredsUser
}
} else {
return err
}
}
n := strconv.FormatInt(time.Now().Unix(), 10)
message := n + channel + productIDs.Join()
hmac, err := crypto.GetHMAC(crypto.HashSHA256,
[]byte(message),
[]byte(creds.Secret))
if err != nil {
return err
}
// TODO: Implement JWT authentication once our REST implementation moves to it, or if there's
// an exchange-wide reform to enable multiple sets of authentication credentials
req := WebsocketRequest{
Type: msgType,
ProductIDs: productIDs.Strings(),
Channel: channel,
Signature: hex.EncodeToString(hmac),
Key: creds.Key,
Timestamp: n,
}
err = c.InitiateRateLimit(context.Background(), WSRate)
if authenticated {
message := n + channel + productIDs.Join()
hmac, err := crypto.GetHMAC(crypto.HashSHA256,

Check failure on line 458 in exchanges/coinbasepro/coinbasepro_websocket.go

View workflow job for this annotation

GitHub Actions / lint

shadow: declaration of "err" shadows declaration at line 437 (govet)
[]byte(message),
[]byte(creds.Secret))
if err != nil {
return err
}
// TODO: Implement JWT authentication once our REST implementation moves to it, or if there's
// an exchange-wide reform to enable multiple sets of authentication credentials
req.Key = creds.Key
req.Signature = hex.EncodeToString(hmac)
err = c.InitiateRateLimit(context.Background(), WSAuthRate)

Check failure on line 468 in exchanges/coinbasepro/coinbasepro_websocket.go

View workflow job for this annotation

GitHub Actions / lint

ineffectual assignment to err (ineffassign)
} else {
err = c.InitiateRateLimit(context.Background(), WSUnauthRate)
}
if err != nil {
return fmt.Errorf("failed to rate limit HTTP request: %w", err)
return fmt.Errorf("failed to rate limit websocket request: %w", err)
}
return c.Websocket.Conn.SendJSONMessage(req)
}
Expand Down
Loading

0 comments on commit 343052b

Please sign in to comment.