package binance import ( "testing" "time" "cryptoHermes/internal/entity" "github.com/stretchr/testify/require" ) func TestParseKlineRow_WellFormed(t *testing.T) { // Binance 12 元数组:[openTime, open, high, low, close, volume, // closeTime, quoteAssetVolume, numberOfTrades, // takerBuyBaseVolume, takerBuyQuoteVolume, ignore] // JSON 解析后数字是 float64 row := []any{ float64(1700000000000), // openTime "50000.00", // open "50500.00", // high "49800.00", // low "50200.00", // close "123.456", // volume float64(1700003599999), // closeTime(过去) "6190000", // quoteVolume float64(789), // tradeCount "60", // takerBuyBase "3000000", // takerBuyQuote "0", // ignore } k, err := parseKlineRow("BTCUSDT", "1h", row) require.NoError(t, err) require.Equal(t, "binance", k.Source) require.Equal(t, "BTCUSDT", k.Symbol) require.Equal(t, "1h", k.Interval) require.Equal(t, int64(1700000000000), k.OpenTime) require.Equal(t, int64(1700003599999), k.CloseTime) require.Equal(t, "50000.00", k.Open) require.Equal(t, "50500.00", k.High) require.Equal(t, "49800.00", k.Low) require.Equal(t, "50200.00", k.Close) require.Equal(t, "123.456", k.Volume) require.Equal(t, "6190000", k.QuoteVolume) require.Equal(t, int64(789), k.TradeCount) require.Equal(t, "60", k.TakerBuyBaseVolume) require.Equal(t, "3000000", k.TakerBuyQuoteVolume) require.True(t, k.IsClosed, "closeTime 2023-11 远小于 now → IsClosed=true") } func TestParseKlineRow_FutureCloseTime(t *testing.T) { future := time.Now().Add(2 * time.Hour).UnixMilli() row := []any{ float64(time.Now().UnixMilli()), "100", "101", "99", "100", "1", float64(future), "100", float64(1), "0.5", "50", "0", } k, err := parseKlineRow("BTCUSDT", "1h", row) require.NoError(t, err) require.False(t, k.IsClosed, "未来 closeTime → 未收线,遵守 G3") } func TestParseKlineRow_ShortArray(t *testing.T) { row := []any{float64(0), "1", "2", "3", "4"} // 只有 5 元 _, err := parseKlineRow("BTCUSDT", "1h", row) require.Error(t, err) require.Contains(t, err.Error(), "5 fields") } func TestParseKlineRow_OpenTimeWrongType(t *testing.T) { row := []any{ "not-a-number", // openTime 应为数字 "1", "2", "3", "4", "5", float64(0), "0", float64(0), "0", "0", "0", } _, err := parseKlineRow("BTCUSDT", "1h", row) require.Error(t, err) require.Contains(t, err.Error(), "openTime") } func TestParseKlineRow_CloseTimeWrongType(t *testing.T) { row := []any{ float64(0), "1", "2", "3", "4", "5", "not-a-number", "0", float64(0), "0", "0", "0", } _, err := parseKlineRow("BTCUSDT", "1h", row) require.Error(t, err) require.Contains(t, err.Error(), "closeTime") } func TestParseKlineRow_TradeCountWrongType(t *testing.T) { row := []any{ float64(0), "1", "2", "3", "4", "5", float64(0), "0", "not-a-number", // tradeCount "0", "0", "0", } _, err := parseKlineRow("BTCUSDT", "1h", row) require.Error(t, err) require.Contains(t, err.Error(), "tradeCount") } // toInt64 全分支:float64 / int64 / int / 未知 func TestToInt64(t *testing.T) { v, err := toInt64(float64(42.7)) require.NoError(t, err) require.Equal(t, int64(42), v) v, err = toInt64(int64(99)) require.NoError(t, err) require.Equal(t, int64(99), v) v, err = toInt64(int(7)) require.NoError(t, err) require.Equal(t, int64(7), v) _, err = toInt64("oops") require.Error(t, err) } // mapLongShort:两种 ratioType 取不同字段 func TestMapLongShort_GlobalAccount(t *testing.T) { dto := []longShortRatioDTO{{ Symbol: "BTCUSDT", LongShortRatio: "1.5", LongAccount: "0.6", ShortAccount: "0.4", LongPosition: "0.7", // 不应被使用 ShortPosition: "0.3", Timestamp: 1700000000000, }} out := mapLongShort(dto, "1h", entity.RatioTypeGlobalAccount) require.Len(t, out, 1) require.Equal(t, "0.6", out[0].LongValue) require.Equal(t, "0.4", out[0].ShortValue) require.Equal(t, "binance", out[0].Source) require.Equal(t, "1h", out[0].Period) require.Equal(t, entity.RatioTypeGlobalAccount, out[0].RatioType) } func TestMapLongShort_TopTraderPosition(t *testing.T) { dto := []longShortRatioDTO{{ Symbol: "ETHUSDT", LongShortRatio: "2.0", LongAccount: "0.5", // 不应被使用 ShortAccount: "0.5", LongPosition: "0.66", ShortPosition: "0.34", Timestamp: 1700000000000, }} out := mapLongShort(dto, "4h", entity.RatioTypeTopTraderPosition) require.Len(t, out, 1) require.Equal(t, "0.66", out[0].LongValue) require.Equal(t, "0.34", out[0].ShortValue) require.Equal(t, entity.RatioTypeTopTraderPosition, out[0].RatioType) } func TestMapLongShort_EmptyInput(t *testing.T) { out := mapLongShort(nil, "1h", entity.RatioTypeGlobalAccount) require.NotNil(t, out) require.Empty(t, out) } // ---- 其余 map* 函数(纯字段平移)------------------------------------ func TestMapTicker(t *testing.T) { d := &tickerDTO{ Symbol: "BTCUSDT", LastPrice: "50000", PriceChange: "100", PriceChangePercent: "0.2", HighPrice: "50500", LowPrice: "49000", Volume: "1234", QuoteVolume: "62000000", OpenTime: 1700000000000, CloseTime: 1700003599999, } out := mapTicker(d) require.Equal(t, "BTCUSDT", out.Symbol) require.Equal(t, "50000", out.LastPrice) require.Equal(t, int64(1700003599999), out.CloseTime) } func TestMapPremiumIndex(t *testing.T) { d := &premiumIndexDTO{ Symbol: "BTCUSDT", NextFundingTime: 1700003600000, LastFundingRate: "0.0001", MarkPrice: "50100", } out := mapPremiumIndex(d) require.Equal(t, "binance", out.Source) require.Equal(t, "0.0001", out.FundingRate) require.Equal(t, "50100", out.MarkPrice) } func TestMapFundingHistory(t *testing.T) { d := []fundingRateDTO{ {Symbol: "BTCUSDT", FundingTime: 1, FundingRate: "0.0001", MarkPrice: "50000"}, {Symbol: "BTCUSDT", FundingTime: 2, FundingRate: "0.0002", MarkPrice: "50100"}, } out := mapFundingHistory(d) require.Len(t, out, 2) require.Equal(t, "0.0002", out[1].FundingRate) } func TestMapOpenInterest(t *testing.T) { d := &openInterestDTO{Symbol: "BTCUSDT", Time: 1700000000000, OpenInterest: "12345"} out := mapOpenInterest(d, "1h") require.Equal(t, "BTCUSDT", out.Symbol) require.Equal(t, "1h", out.Period) require.Equal(t, "12345", out.OpenInterest) } func TestMapOpenInterestHistory(t *testing.T) { d := []openInterestHistDTO{ {Symbol: "BTCUSDT", Timestamp: 1, SumOpenInterest: "100", SumOpenInterestValue: "5000000"}, } out := mapOpenInterestHistory(d, "4h") require.Len(t, out, 1) require.Equal(t, "4h", out[0].Period) require.Equal(t, "100", out[0].OpenInterest) require.Equal(t, "5000000", out[0].OpenInterestValue) } func TestMapTakerVolume(t *testing.T) { d := []takerVolumeDTO{ {Timestamp: 1, BuySellRatio: "1.2", BuyVol: "60", SellVol: "50"}, } out := mapTakerVolume(d, "BTCUSDT", "1h") require.Len(t, out, 1) require.Equal(t, "BTCUSDT", out[0].Symbol) require.Equal(t, "1.2", out[0].BuySellRatio) require.Equal(t, "60", out[0].BuyVolume) require.Equal(t, "50", out[0].SellVolume) }