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 } func (m *resolverMarketStub) GetFuturePrice(symbol string) (float64, float64, int64, bool) { return 0, 0, 0, false } func (m *resolverMarketStub) GetAllPremiumIndex() (map[string]market.PremiumIndex, error) { return nil, nil } 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 m.alphaTokens[symbol] } func (m *resolverMarketStub) GetAlphaToken(symbol string) (market.AlphaTokenInfo, bool) { return market.AlphaTokenInfo{}, false } func (m *resolverMarketStub) GetAlphaPrice(symbol string) (float64, bool) { return 0, false } func (m *resolverMarketStub) IsSpotPair(symbol string) bool { return m.spotPairs[symbol] } func (m *resolverMarketStub) IsFuturesPair(symbol string) bool { return m.futuresPairs[symbol] } func (m *resolverMarketStub) RefreshTradingPairCache() error { return nil } func withResolverMarketStub(t *testing.T, marketStub *resolverMarketStub) { t.Helper() orig := data.Market data.Market = marketStub t.Cleanup(func() { data.Market = orig }) } func TestToken2FutureSymbols_ResolvesPrefixAndSuffix(t *testing.T) { withResolverMarketStub(t, &resolverMarketStub{ futuresPairs: map[string]bool{ "1000PEPEUSDT": true, "1000PEPEUSDC": true, }, }) syms := Token2FutureSymbols("pepe") if len(syms) == 0 { t.Fatalf("expected future symbols for PEPE") } foundUSDT := false foundUSDC := false for _, sym := range syms { if sym == "1000PEPEUSDT" { foundUSDT = true } if sym == "1000PEPEUSDC" { foundUSDC = true } } if !foundUSDT || !foundUSDC { t.Fatalf("expected both 1000PEPEUSDT and 1000PEPEUSDC, got %+v", syms) } } func TestToken2FutureSymbols_UsesDeterministicCandidateOrder(t *testing.T) { withResolverMarketStub(t, &resolverMarketStub{ futuresPairs: map[string]bool{ "1000PEPEUSDT": true, "1000PEPEUSDC": true, "PEPEUSDT": true, "PEPEUSDC": true, }, }) 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" { t.Fatalf("expected [LUNAUSDT], got %+v", spots) } } func TestToken2SpotSymbols_SpotOnlyFallback(t *testing.T) { withResolverMarketStub(t, &resolverMarketStub{ spotPairs: map[string]bool{"ABCUSDT": true}, }) spots := Token2SpotSymbols("abc") if len(spots) != 1 || spots[0] != "ABCUSDT" { t.Fatalf("expected [ABCUSDT], got %+v", spots) } }