package subscription import ( "errors" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/thrasher-corp/gocryptotrader/common" "github.com/thrasher-corp/gocryptotrader/currency" "github.com/thrasher-corp/gocryptotrader/exchanges/asset" "github.com/thrasher-corp/gocryptotrader/exchanges/kline" ) // TestExpandTemplates exercises ExpandTemplates func TestExpandTemplates(t *testing.T) { t.Parallel() e := newMockEx() e.tpl = "subscriptions.tmpl" // Functionality tests l := List{ {Channel: "single-channel"}, {Channel: "expand-assets", Asset: asset.All, Interval: kline.FifteenMin}, {Channel: "expand-pairs", Asset: asset.All, Levels: 1}, {Channel: "expand-pairs", Asset: asset.Spot, Levels: 2}, {Channel: "single-channel", QualifiedChannel: "just one sub already processed"}, {Channel: "update-asset-pairs", Asset: asset.All}, {Channel: "expand-pairs", Asset: asset.Spot, Pairs: e.pairs[asset.Spot][0:2], Levels: 3}, {Channel: "batching", Asset: asset.Spot}, {Channel: "single-channel", Authenticated: true}, } _, err := l.ExpandTemplates(&mockExWithSubValidator{mockEx: e, GenerateBadSubscription: true}) require.ErrorIs(t, err, errValidateSubscriptionsTestError) _, err = l.ExpandTemplates(&mockExWithSubValidator{mockEx: e}) require.NoError(t, err) _, err = l.ExpandTemplates(&mockExWithSubValidator{mockEx: e, FailGetSubscriptions: true}) require.ErrorIs(t, err, ErrNotFound) got, err := l.ExpandTemplates(e) require.NoError(t, err, "ExpandTemplates must not error") exp := List{ {Channel: "single-channel", QualifiedChannel: "single-channel"}, {Channel: "expand-assets", QualifiedChannel: "spot-expand-assets@15m", Asset: asset.Spot, Pairs: e.pairs[asset.Spot], Interval: kline.FifteenMin}, {Channel: "expand-assets", QualifiedChannel: "future-expand-assets@15m", Asset: asset.Futures, Pairs: e.pairs[asset.Futures], Interval: kline.FifteenMin}, {Channel: "single-channel", QualifiedChannel: "just one sub already processed"}, {Channel: "update-asset-pairs", QualifiedChannel: "spot-btcusdt-update-asset-pairs", Asset: asset.Spot, Pairs: currency.Pairs{btcusdtPair}}, {Channel: "expand-pairs", QualifiedChannel: "spot-USDTBTC-expand-pairs@3", Asset: asset.Spot, Pairs: e.pairs[asset.Spot][1:2], Levels: 3}, {Channel: "expand-pairs", QualifiedChannel: "spot-USDCETH-expand-pairs@3", Asset: asset.Spot, Pairs: e.pairs[asset.Spot][:1], Levels: 3}, } for a, pairs := range e.pairs { if a == asset.Index { // Not IsAssetWebsocketEnabled continue } for _, p := range common.SortStrings(pairs) { pStr := p.Swap().String() if a == asset.Spot { exp = append(exp, List{ {Channel: "expand-pairs", QualifiedChannel: "spot-" + pStr + "-expand-pairs@1", Asset: a, Pairs: currency.Pairs{p}, Levels: 1}, &Subscription{Channel: "expand-pairs", QualifiedChannel: "spot-" + pStr + "-expand-pairs@2", Asset: a, Pairs: currency.Pairs{p}, Levels: 2}, }...) } else { exp = append(exp, &Subscription{Channel: "expand-pairs", QualifiedChannel: "future-" + pStr + "-expand-pairs@1", Asset: a, Pairs: currency.Pairs{p}, Levels: 1}, ) } } } for _, b := range common.Batch(common.SortStrings(e.pairs[asset.Spot]), 3) { exp = append(exp, &Subscription{Channel: "batching", QualifiedChannel: "spot-" + b.Join() + "-batching", Asset: asset.Spot, Pairs: b}) } if !equalLists(t, exp, got) { t.FailNow() // If the first list isn't equal testing it again will duplicate test failures } e.auth = true got, err = l.ExpandTemplates(e) require.NoError(t, err, "ExpandTemplates must not error") exp = append(exp, &Subscription{Channel: "single-channel", QualifiedChannel: "single-channel-authed"}) equalLists(t, exp, got) // Test with just one asset to ensure asset.All works, and disabled assets don't error e.assets = e.assets[:1] l = List{ {Channel: "expand-assets", Asset: asset.All, Interval: kline.OneHour}, {Channel: "expand-pairs", Asset: asset.All, Levels: 4}, {Channel: "single-channel", Asset: asset.Futures}, } got, err = l.ExpandTemplates(e) require.NoError(t, err, "ExpandTemplates must not error") exp = List{ {Channel: "expand-assets", QualifiedChannel: "spot-expand-assets@1h", Asset: asset.Spot, Pairs: e.pairs[asset.Spot], Interval: kline.OneHour}, } for _, p := range e.pairs[asset.Spot] { exp = append(exp, List{ {Channel: "expand-pairs", QualifiedChannel: "spot-" + p.Swap().String() + "-expand-pairs@4", Asset: asset.Spot, Pairs: currency.Pairs{p}, Levels: 4}, }...) } equalLists(t, exp, got) // Users can specify pairs which aren't available, even across diverse assets // Use-case: Coinbase user sub for futures BTC-USD would return all BTC pairs and all USD pairs, even though BTC-USD might not be enabled or available p := currency.Pairs{currency.NewPairWithDelimiter("BEAR", "PEAR", "🐻")} got, err = List{{Channel: "expand-pairs", Asset: asset.All, Pairs: p}}.ExpandTemplates(e) require.NoError(t, err, "Must not error with fictional pairs") exp = List{{Channel: "expand-pairs", QualifiedChannel: "spot-PEARBEAR-expand-pairs@0", Asset: asset.Spot, Pairs: p}} equalLists(t, exp, got) // Error cases _, err = List{{Channel: "nil"}}.ExpandTemplates(e) assert.ErrorIs(t, err, errInvalidTemplate, "Should get correct error on nil template") e.tpl = "errors.tmpl" _, err = List{{Channel: "error1"}}.ExpandTemplates(e) assert.ErrorContains(t, err, "wrong number of args for String", "Should error correctly with execution error") _, err = List{{Channel: "empty-content", Asset: asset.Spot}}.ExpandTemplates(e) assert.ErrorIs(t, err, errNoTemplateContent, "Should error correctly when no content generated") assert.ErrorContains(t, err, "empty-content", "Should error correctly when no content generated") _, err = List{{Channel: "error2", Asset: asset.All}}.ExpandTemplates(e) assert.ErrorIs(t, err, errAssetRecords, "Should error correctly when invalid number of asset entries") _, err = List{{Channel: "error3", Asset: asset.Spot}}.ExpandTemplates(e) assert.ErrorIs(t, err, errPairRecords, "Should error correctly when invalid number of pair entries") _, err = List{{Channel: "error4", Asset: asset.Spot}}.ExpandTemplates(e) assert.ErrorIs(t, err, errTooManyBatchSizePerAsset, "Should error correctly when too many BatchSize directives") _, err = List{{Channel: "error5", Asset: asset.Spot}}.ExpandTemplates(e) assert.ErrorIs(t, err, common.ErrTypeAssertFailure, "Should error correctly when batch size isn't an int") e.tpl = "parse-error.tmpl" e.tpl = "parse-error.tmpl" _, err = l.ExpandTemplates(e) assert.ErrorContains(t, err, "function \"explode\" not defined", "Should error correctly on unparsable template") e.errFormat = errors.New("the planet Krypton is gone") _, err = l.ExpandTemplates(e) assert.ErrorIs(t, err, e.errFormat, "Should error correctly on GetPairFormat") e.errPairs = errors.New("bad parenting from Jor-El") _, err = l.ExpandTemplates(e) assert.ErrorIs(t, err, e.errPairs, "Should error correctly on GetEnabledPairs") l = List{{Channel: "single-channel", QualifiedChannel: "already happy"}} got, err = l.ExpandTemplates(e) require.NoError(t, err) require.Len(t, got, 1, "Must get back the one sub") assert.Equal(t, "already happy", l[0].QualifiedChannel, "Should get back the one sub") assert.NotSame(t, &got, &l, "Should get back a different actual list") _, err = List{{Channel: "nil"}}.ExpandTemplates(e) assert.ErrorIs(t, err, errInvalidTemplate, "Should get correct error on nil template") }