mirror of
https://github.com/d0zingcat/gocryptotrader.git
synced 2026-05-13 15:09:42 +00:00
* tests: Replace !errors.Is(err, target) with testify equivalents * codebase: Manual !errors.Is(err, target) replacements * typo: Replace errMisMatchedEvent with errMismatchedEvent * tests: Enhance error messages for better output * tests: Refactor error assertions in various test cases to use require and improve clarity * misc linter: Fix assert should wording * tests: Simplify assertions in TestCreateSignals for clarity and conciseness * tests: Enhance assertion message in TestCreateSignals
491 lines
14 KiB
Go
491 lines
14 KiB
Go
package engine
|
|
|
|
import (
|
|
"os"
|
|
"slices"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
"github.com/thrasher-corp/gocryptotrader/config"
|
|
exchange "github.com/thrasher-corp/gocryptotrader/exchanges"
|
|
"github.com/thrasher-corp/gocryptotrader/exchanges/bitfinex"
|
|
"github.com/thrasher-corp/gocryptotrader/exchanges/bitstamp"
|
|
)
|
|
|
|
// blockedCIExchanges are exchanges that are not able to be tested on CI
|
|
var blockedCIExchanges = []string{
|
|
"binance", // binance API is banned from executing within the US where github Actions is ran
|
|
"bybit", // bybit API is banned from executing within the US where github Actions is ran
|
|
}
|
|
|
|
func isCITest() bool {
|
|
return os.Getenv("CI") == "true"
|
|
}
|
|
|
|
func TestLoadConfigWithSettings(t *testing.T) {
|
|
empty := ""
|
|
somePath := "somePath"
|
|
// Clean up after the tests
|
|
defer os.RemoveAll(somePath)
|
|
tests := []struct {
|
|
name string
|
|
flags []string
|
|
settings *Settings
|
|
want *string
|
|
wantErr bool
|
|
}{
|
|
{
|
|
name: "invalid file",
|
|
settings: &Settings{
|
|
ConfigFile: "nonExistent.json",
|
|
},
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "test file",
|
|
settings: &Settings{
|
|
ConfigFile: config.TestFile,
|
|
CoreSettings: CoreSettings{EnableDryRun: true},
|
|
},
|
|
want: &empty,
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "data dir in settings overrides config data dir",
|
|
flags: []string{"datadir"},
|
|
settings: &Settings{
|
|
ConfigFile: config.TestFile,
|
|
DataDir: somePath,
|
|
CoreSettings: CoreSettings{EnableDryRun: true},
|
|
},
|
|
want: &somePath,
|
|
wantErr: false,
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
// prepare the 'flags'
|
|
flagSet := make(map[string]bool)
|
|
for _, v := range tt.flags {
|
|
flagSet[v] = true
|
|
}
|
|
// Run the test
|
|
got, err := loadConfigWithSettings(tt.settings, flagSet)
|
|
if (err != nil) != tt.wantErr {
|
|
t.Errorf("loadConfigWithSettings() error = %v, wantErr %v", err, tt.wantErr)
|
|
return
|
|
}
|
|
if got != nil || tt.want != nil {
|
|
if (got == nil && tt.want != nil) || (got != nil && tt.want == nil) {
|
|
t.Errorf("loadConfigWithSettings() = is nil %v, want nil %v", got == nil, tt.want == nil)
|
|
} else if got.DataDirectory != *tt.want {
|
|
t.Errorf("loadConfigWithSettings() = %v, want %v", got.DataDirectory, *tt.want)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestStartStopDoesNotCausePanic(t *testing.T) {
|
|
t.Parallel()
|
|
tempDir := t.TempDir()
|
|
botOne, err := NewFromSettings(&Settings{
|
|
ConfigFile: config.TestFile,
|
|
CoreSettings: CoreSettings{EnableDryRun: true},
|
|
DataDir: tempDir,
|
|
}, nil)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
botOne.Settings.EnableGRPCProxy = false
|
|
for i := range botOne.Config.Exchanges {
|
|
if botOne.Config.Exchanges[i].Name != testExchange {
|
|
// there is no need to load all exchanges for this test
|
|
botOne.Config.Exchanges[i].Enabled = false
|
|
}
|
|
}
|
|
if err = botOne.Start(); err != nil {
|
|
t.Error(err)
|
|
}
|
|
|
|
botOne.Stop()
|
|
}
|
|
|
|
var enableExperimentalTest = false
|
|
|
|
func TestStartStopTwoDoesNotCausePanic(t *testing.T) {
|
|
t.Parallel()
|
|
if !enableExperimentalTest {
|
|
t.Skip("test is functional, however does not need to be included in go test runs")
|
|
}
|
|
tempDir := t.TempDir()
|
|
tempDir2 := t.TempDir()
|
|
botOne, err := NewFromSettings(&Settings{
|
|
ConfigFile: config.TestFile,
|
|
CoreSettings: CoreSettings{EnableDryRun: true},
|
|
DataDir: tempDir,
|
|
}, nil)
|
|
if err != nil {
|
|
t.Error(err)
|
|
}
|
|
botOne.Settings.EnableGRPCProxy = false
|
|
|
|
botTwo, err := NewFromSettings(&Settings{
|
|
ConfigFile: config.TestFile,
|
|
CoreSettings: CoreSettings{EnableDryRun: true},
|
|
DataDir: tempDir2,
|
|
}, nil)
|
|
if err != nil {
|
|
t.Error(err)
|
|
}
|
|
botTwo.Settings.EnableGRPCProxy = false
|
|
|
|
if err = botOne.Start(); err != nil {
|
|
t.Error(err)
|
|
}
|
|
if err = botTwo.Start(); err != nil {
|
|
t.Error(err)
|
|
}
|
|
|
|
botOne.Stop()
|
|
botTwo.Stop()
|
|
}
|
|
|
|
func TestGetExchangeByName(t *testing.T) {
|
|
t.Parallel()
|
|
_, err := (*ExchangeManager)(nil).GetExchangeByName("tehehe")
|
|
assert.ErrorIs(t, err, ErrNilSubsystem)
|
|
|
|
em := NewExchangeManager()
|
|
exch, err := em.NewExchangeByName(testExchange)
|
|
require.NoError(t, err)
|
|
|
|
exch.SetDefaults()
|
|
exch.SetEnabled(true)
|
|
err = em.Add(exch)
|
|
require.NoError(t, err)
|
|
|
|
e := &Engine{ExchangeManager: em}
|
|
|
|
if !exch.IsEnabled() {
|
|
t.Errorf("TestGetExchangeByName: Unexpected result")
|
|
}
|
|
|
|
exch.SetEnabled(false)
|
|
bfx, err := e.GetExchangeByName(testExchange)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if bfx.IsEnabled() {
|
|
t.Errorf("TestGetExchangeByName: Unexpected result")
|
|
}
|
|
if exch.GetName() != testExchange {
|
|
t.Errorf("TestGetExchangeByName: Unexpected result")
|
|
}
|
|
|
|
_, err = e.GetExchangeByName("Asdasd")
|
|
assert.ErrorIs(t, err, ErrExchangeNotFound)
|
|
}
|
|
|
|
func TestUnloadExchange(t *testing.T) {
|
|
t.Parallel()
|
|
em := NewExchangeManager()
|
|
exch, err := em.NewExchangeByName(testExchange)
|
|
require.NoError(t, err)
|
|
|
|
exch.SetDefaults()
|
|
exch.SetEnabled(true)
|
|
err = em.Add(exch)
|
|
require.NoError(t, err)
|
|
|
|
e := &Engine{
|
|
ExchangeManager: em,
|
|
Config: &config.Config{Exchanges: []config.Exchange{{Name: testExchange}}},
|
|
}
|
|
err = e.UnloadExchange("asdf")
|
|
assert.ErrorIs(t, err, config.ErrExchangeNotFound)
|
|
|
|
err = e.UnloadExchange(testExchange)
|
|
if err != nil {
|
|
t.Errorf("TestUnloadExchange: Failed to get exchange. %s",
|
|
err)
|
|
}
|
|
|
|
err = e.UnloadExchange(testExchange)
|
|
assert.ErrorIs(t, err, ErrExchangeNotFound)
|
|
}
|
|
|
|
func TestDryRunParamInteraction(t *testing.T) {
|
|
t.Parallel()
|
|
bot := &Engine{
|
|
ExchangeManager: NewExchangeManager(),
|
|
Settings: Settings{},
|
|
Config: &config.Config{
|
|
Exchanges: []config.Exchange{
|
|
{
|
|
Name: testExchange,
|
|
WebsocketTrafficTimeout: time.Second,
|
|
},
|
|
},
|
|
},
|
|
}
|
|
err := bot.LoadExchange(testExchange)
|
|
assert.NoError(t, err, "LoadExchange should not error")
|
|
|
|
exchCfg, err := bot.Config.GetExchangeConfig(testExchange)
|
|
if err != nil {
|
|
t.Error(err)
|
|
}
|
|
if exchCfg.Verbose {
|
|
t.Error("verbose should have been disabled")
|
|
}
|
|
if err = bot.UnloadExchange(testExchange); err != nil {
|
|
t.Error(err)
|
|
}
|
|
|
|
// Now set dryrun mode to true,
|
|
// enable exchange verbose mode and verify that verbose mode
|
|
// will be set on Bitfinex
|
|
bot.Settings.EnableDryRun = true
|
|
bot.Settings.CheckParamInteraction = true
|
|
bot.Settings.EnableExchangeVerbose = true
|
|
|
|
err = bot.LoadExchange(testExchange)
|
|
assert.NoError(t, err, "LoadExchange should not error")
|
|
|
|
exchCfg, err = bot.Config.GetExchangeConfig(testExchange)
|
|
if err != nil {
|
|
t.Error(err)
|
|
}
|
|
if !bot.Settings.EnableDryRun ||
|
|
!exchCfg.Verbose {
|
|
t.Error("dryrun should be true and verbose should be true")
|
|
}
|
|
}
|
|
|
|
func TestFlagSetWith(t *testing.T) {
|
|
var isRunning bool
|
|
flags := make(FlagSet)
|
|
// Flag not set default to config
|
|
flags.WithBool("NOT SET", &isRunning, true)
|
|
if !isRunning {
|
|
t.Fatalf("received: '%v' but expected: '%v'", isRunning, true)
|
|
}
|
|
flags.WithBool("NOT SET", &isRunning, false)
|
|
if isRunning {
|
|
t.Fatalf("received: '%v' but expected: '%v'", isRunning, false)
|
|
}
|
|
|
|
flags["IS SET"] = true
|
|
isRunning = true
|
|
// Flag set true which will override config
|
|
flags.WithBool("IS SET", &isRunning, true)
|
|
if !isRunning {
|
|
t.Fatalf("received: '%v' but expected: '%v'", isRunning, true)
|
|
}
|
|
flags.WithBool("IS SET", &isRunning, false)
|
|
if !isRunning {
|
|
t.Fatalf("received: '%v' but expected: '%v'", isRunning, true)
|
|
}
|
|
|
|
flags["IS SET"] = true
|
|
isRunning = false
|
|
// Flag set false which will override config
|
|
flags.WithBool("IS SET", &isRunning, true)
|
|
if isRunning {
|
|
t.Fatalf("received: '%v' but expected: '%v'", isRunning, false)
|
|
}
|
|
flags.WithBool("IS SET", &isRunning, false)
|
|
if isRunning {
|
|
t.Fatalf("received: '%v' but expected: '%v'", isRunning, false)
|
|
}
|
|
}
|
|
|
|
func TestRegisterWebsocketDataHandler(t *testing.T) {
|
|
t.Parallel()
|
|
var e *Engine
|
|
err := e.RegisterWebsocketDataHandler(nil, false)
|
|
require.ErrorIs(t, err, errNilBot)
|
|
|
|
e = &Engine{WebsocketRoutineManager: &WebsocketRoutineManager{}}
|
|
err = e.RegisterWebsocketDataHandler(func(_ string, _ any) error { return nil }, false)
|
|
require.NoError(t, err)
|
|
}
|
|
|
|
func TestSetDefaultWebsocketDataHandler(t *testing.T) {
|
|
t.Parallel()
|
|
var e *Engine
|
|
err := e.SetDefaultWebsocketDataHandler()
|
|
require.ErrorIs(t, err, errNilBot)
|
|
|
|
e = &Engine{WebsocketRoutineManager: &WebsocketRoutineManager{}}
|
|
err = e.SetDefaultWebsocketDataHandler()
|
|
require.NoError(t, err)
|
|
}
|
|
|
|
func TestSettingsPrint(t *testing.T) {
|
|
t.Parallel()
|
|
var s *Settings
|
|
s.PrintLoadedSettings()
|
|
|
|
s = &Settings{}
|
|
s.PrintLoadedSettings()
|
|
}
|
|
|
|
var unsupportedDefaultConfigExchanges = []string{
|
|
"poloniex", // poloniex has dropped support for the API GCT has implemented //TODO: drop this when supported
|
|
"coinbasepro", // deprecated API. TODO: Remove this when the Coinbase update is merged
|
|
}
|
|
|
|
func TestGetDefaultConfigurations(t *testing.T) {
|
|
t.Parallel()
|
|
em := NewExchangeManager()
|
|
for i := range exchange.Exchanges {
|
|
name := strings.ToLower(exchange.Exchanges[i])
|
|
t.Run(name, func(t *testing.T) {
|
|
t.Parallel()
|
|
exch, err := em.NewExchangeByName(name)
|
|
require.NoError(t, err, "NewExchangeByName must not error")
|
|
|
|
if isCITest() && slices.Contains(blockedCIExchanges, name) {
|
|
t.Skipf("skipping %s due to CI test restrictions", name)
|
|
}
|
|
|
|
if slices.Contains(unsupportedDefaultConfigExchanges, name) {
|
|
t.Skipf("skipping %s unsupported", name)
|
|
}
|
|
|
|
defaultCfg, err := exchange.GetDefaultConfig(t.Context(), exch)
|
|
require.NoError(t, err, "GetDefaultConfig must not error")
|
|
require.NotNil(t, defaultCfg)
|
|
assert.NotEmpty(t, defaultCfg.Name, "Name should not be empty")
|
|
assert.True(t, defaultCfg.Enabled, "Enabled should have the correct value")
|
|
|
|
if exch.SupportsWebsocket() {
|
|
assert.Positive(t, defaultCfg.WebsocketResponseCheckTimeout, "WebsocketResponseCheckTimeout should be positive")
|
|
assert.Positive(t, defaultCfg.WebsocketResponseMaxLimit, "WebsocketResponseMaxLimit should be positive")
|
|
assert.Positive(t, defaultCfg.WebsocketTrafficTimeout, "WebsocketTrafficTimeout should be positive")
|
|
}
|
|
|
|
require.NoError(t, exch.Setup(defaultCfg), "Setup must not error")
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestSetupExchanges(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
t.Run("No enabled exchanges", func(t *testing.T) {
|
|
t.Parallel()
|
|
e := &Engine{
|
|
Config: &config.Config{Exchanges: []config.Exchange{{Name: testExchange}}},
|
|
}
|
|
assert.ErrorIs(t, e.SetupExchanges(), ErrNoExchangesLoaded)
|
|
})
|
|
|
|
t.Run("EnableAllExchanges with specific exchanges set", func(t *testing.T) {
|
|
t.Parallel()
|
|
e := &Engine{
|
|
Config: &config.Config{},
|
|
Settings: Settings{
|
|
CoreSettings: CoreSettings{
|
|
EnableAllExchanges: true,
|
|
Exchanges: "Bitstamp,Bitfinex",
|
|
},
|
|
},
|
|
}
|
|
assert.EqualError(t, e.SetupExchanges(), "cannot enable all exchanges and specific exchanges concurrently")
|
|
})
|
|
|
|
t.Run("Settings dry run toggling", func(t *testing.T) {
|
|
t.Parallel()
|
|
e := &Engine{
|
|
Config: &config.Config{},
|
|
Settings: Settings{
|
|
CoreSettings: CoreSettings{
|
|
EnableAllPairs: true,
|
|
EnableAllExchanges: true,
|
|
},
|
|
ExchangeTuningSettings: ExchangeTuningSettings{
|
|
EnableExchangeVerbose: true,
|
|
EnableExchangeWebsocketSupport: true,
|
|
EnableExchangeAutoPairUpdates: true,
|
|
DisableExchangeAutoPairUpdates: true,
|
|
HTTPUserAgent: "test",
|
|
HTTPProxy: "test",
|
|
HTTPTimeout: 1,
|
|
EnableExchangeHTTPDebugging: true,
|
|
},
|
|
},
|
|
}
|
|
assert.ErrorIs(t, e.SetupExchanges(), ErrNoExchangesLoaded)
|
|
assert.False(t, e.Settings.EnableDryRun)
|
|
e.Settings.CheckParamInteraction = true
|
|
assert.ErrorIs(t, e.SetupExchanges(), ErrNoExchangesLoaded)
|
|
assert.True(t, e.Settings.EnableDryRun)
|
|
})
|
|
|
|
// Test that overridden exchange inputs are handled correctly
|
|
testCases := []struct {
|
|
name string
|
|
exchangeString string
|
|
expectedError string
|
|
}{
|
|
{"Invalid exchange pair", "bob|jill", "exchange bob|jill not found"},
|
|
{"Single invalid exchange", "bob", "exchange bob not found"},
|
|
{"Mixed valid and invalid exchanges", "bob,bitstamp", "exchange bob not found"},
|
|
{"Valid exchange", "BiTSTaMp", "no exchanges have been loaded"}, // Proper exchange name, but not loaded
|
|
}
|
|
|
|
for _, tc := range testCases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
t.Parallel()
|
|
e := &Engine{
|
|
Config: &config.Config{},
|
|
Settings: Settings{CoreSettings: CoreSettings{Exchanges: tc.exchangeString}},
|
|
}
|
|
assert.ErrorContains(t, e.SetupExchanges(), tc.expectedError)
|
|
})
|
|
}
|
|
|
|
t.Run("Two valid exchanges with exchanges flag toggled", func(t *testing.T) {
|
|
t.Parallel()
|
|
e := &Engine{Config: &config.Config{}}
|
|
|
|
exchLoader := func(exch exchange.IBotExchange) {
|
|
exch.SetDefaults()
|
|
exch.GetBase().Features.Supports.RESTCapabilities.AutoPairUpdates = false
|
|
cfg, err := exchange.GetDefaultConfig(t.Context(), exch)
|
|
require.NoError(t, err)
|
|
e.Config.Exchanges = append(e.Config.Exchanges, *cfg)
|
|
}
|
|
|
|
e.ExchangeManager = NewExchangeManager()
|
|
exchLoader(new(bitstamp.Bitstamp))
|
|
exchLoader(new(bitfinex.Bitfinex))
|
|
assert.ElementsMatch(t, []string{"Bitstamp", "Bitfinex"}, e.Config.GetEnabledExchanges())
|
|
|
|
t.Run("Load specific exchange", func(t *testing.T) {
|
|
e.Settings.Exchanges = "BiTfInEx"
|
|
assert.NoError(t, e.SetupExchanges(), "SetupExchanges with a valid exchange should not error")
|
|
exchanges, err := e.ExchangeManager.GetExchanges()
|
|
require.NoError(t, err)
|
|
require.Len(t, exchanges, 1)
|
|
assert.Equal(t, "Bitfinex", exchanges[0].GetName())
|
|
})
|
|
|
|
t.Run("Load all enabled exchanges", func(t *testing.T) {
|
|
e.Settings.Exchanges = ""
|
|
assert.NoError(t, e.SetupExchanges(), "SetupExchanges with all enabled exchanges should not error")
|
|
exchanges, err := e.ExchangeManager.GetExchanges()
|
|
require.NoError(t, err)
|
|
require.Len(t, exchanges, 2)
|
|
exchangeNames := []string{exchanges[0].GetName(), exchanges[1].GetName()}
|
|
assert.ElementsMatch(t, []string{"Bitstamp", "Bitfinex"}, exchangeNames)
|
|
})
|
|
})
|
|
}
|