diff --git a/internal/helper/binancex/resolver_test.go b/internal/helper/binancex/resolver_test.go index 5e0767c..b3f13fc 100644 --- a/internal/helper/binancex/resolver_test.go +++ b/internal/helper/binancex/resolver_test.go @@ -1,13 +1,16 @@ package binancex import ( + "reflect" "testing" + "me.thuanle/bbot/internal/configs/binance" "me.thuanle/bbot/internal/data" "me.thuanle/bbot/internal/data/market" ) type resolverMarketStub struct { + alphaTokens map[string]bool spotPairs map[string]bool futuresPairs map[string]bool } @@ -21,7 +24,7 @@ func (m *resolverMarketStub) GetAllPremiumIndex() (map[string]market.PremiumInde func (m *resolverMarketStub) GetAllFundRate() (map[string]float64, map[string]int64) { return nil, nil } func (m *resolverMarketStub) GetSpotPrice(symbol string) (float64, bool) { return 0, false } func (m *resolverMarketStub) GetMarginInterestRates() map[string]float64 { return nil } -func (m *resolverMarketStub) IsAlphaToken(symbol string) bool { return false } +func (m *resolverMarketStub) IsAlphaToken(symbol string) bool { return m.alphaTokens[symbol] } func (m *resolverMarketStub) GetAlphaToken(symbol string) (market.AlphaTokenInfo, bool) { return market.AlphaTokenInfo{}, false } @@ -30,16 +33,20 @@ func (m *resolverMarketStub) IsSpotPair(symbol string) bool { retu func (m *resolverMarketStub) IsFuturesPair(symbol string) bool { return m.futuresPairs[symbol] } func (m *resolverMarketStub) RefreshTradingPairCache() error { return nil } -func TestToken2FutureSymbols_ResolvesPrefixAndSuffix(t *testing.T) { +func withResolverMarketStub(t *testing.T, marketStub *resolverMarketStub) { + t.Helper() orig := data.Market - defer func() { data.Market = orig }() + data.Market = marketStub + t.Cleanup(func() { data.Market = orig }) +} - data.Market = &resolverMarketStub{ +func TestToken2FutureSymbols_ResolvesPrefixAndSuffix(t *testing.T) { + withResolverMarketStub(t, &resolverMarketStub{ futuresPairs: map[string]bool{ "1000PEPEUSDT": true, "1000PEPEUSDC": true, }, - } + }) syms := Token2FutureSymbols("pepe") if len(syms) == 0 { @@ -62,14 +69,99 @@ func TestToken2FutureSymbols_ResolvesPrefixAndSuffix(t *testing.T) { } } -func TestToken2SpotSymbols_AppliesExplicitRemap(t *testing.T) { - orig := data.Market - defer func() { data.Market = orig }() +func TestToken2FutureSymbols_UsesDeterministicCandidateOrder(t *testing.T) { + withResolverMarketStub(t, &resolverMarketStub{ + futuresPairs: map[string]bool{ + "1000PEPEUSDT": true, + "1000PEPEUSDC": true, + "PEPEUSDT": true, + "PEPEUSDC": true, + }, + }) - data.Market = &resolverMarketStub{ + syms := Token2FutureSymbols("pepe") + expected := []string{"1000PEPEUSDT", "1000PEPEUSDC", "PEPEUSDT", "PEPEUSDC"} + if !reflect.DeepEqual(syms, expected) { + t.Fatalf("expected deterministic order %+v, got %+v", expected, syms) + } +} + +func TestToken2FutureSymbols_ResolvesUSDCAbbreviation(t *testing.T) { + withResolverMarketStub(t, &resolverMarketStub{ + futuresPairs: map[string]bool{"PEPEUSDC": true}, + }) + + syms := Token2FutureSymbols("pepec") + expected := []string{"PEPEUSDC"} + if !reflect.DeepEqual(syms, expected) { + t.Fatalf("expected USDC abbreviation resolution %+v, got %+v", expected, syms) + } +} + +func TestSymbolSuffixListMatchesSymbolSuffixMap(t *testing.T) { + seen := make(map[string]struct{}, len(binance.SymbolSuffixList)) + for _, suffix := range binance.SymbolSuffixList { + if _, ok := binance.SymbolSuffixMap[suffix]; !ok { + t.Fatalf("SymbolSuffixList contains %q missing from SymbolSuffixMap", suffix) + } + seen[suffix] = struct{}{} + } + + for suffix := range binance.SymbolSuffixMap { + if _, ok := seen[suffix]; !ok { + t.Fatalf("SymbolSuffixMap contains %q missing from SymbolSuffixList", suffix) + } + } +} + +func TestIsToken(t *testing.T) { + tests := []struct { + name string + input string + marketStub *resolverMarketStub + want bool + }{ + { + name: "futures token resolution success", + input: "pepe", + marketStub: &resolverMarketStub{ + futuresPairs: map[string]bool{"1000PEPEUSDT": true}, + }, + want: true, + }, + { + name: "alpha token fallback", + input: "alpha", + marketStub: &resolverMarketStub{ + alphaTokens: map[string]bool{"ALPHA": true}, + }, + want: true, + }, + { + name: "non alphanumeric input", + input: "bad!", + marketStub: &resolverMarketStub{}, + want: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + withResolverMarketStub(t, tt.marketStub) + + got := IsToken(tt.input) + if got != tt.want { + t.Fatalf("expected %v, got %v", tt.want, got) + } + }) + } +} + +func TestToken2SpotSymbols_AppliesExplicitRemap(t *testing.T) { + withResolverMarketStub(t, &resolverMarketStub{ futuresPairs: map[string]bool{"LUNA2USDT": true}, spotPairs: map[string]bool{"LUNAUSDT": true}, - } + }) spots := Token2SpotSymbols("luna2") if len(spots) != 1 || spots[0] != "LUNAUSDT" { @@ -78,12 +170,9 @@ func TestToken2SpotSymbols_AppliesExplicitRemap(t *testing.T) { } func TestToken2SpotSymbols_SpotOnlyFallback(t *testing.T) { - orig := data.Market - defer func() { data.Market = orig }() - - data.Market = &resolverMarketStub{ + withResolverMarketStub(t, &resolverMarketStub{ spotPairs: map[string]bool{"ABCUSDT": true}, - } + }) spots := Token2SpotSymbols("abc") if len(spots) != 1 || spots[0] != "ABCUSDT" {