test(binance): mapper.go 全函数 100% 覆盖

- parseKlineRow:正例 + 短数组 + future closeTime(G3 保护) + openTime/
  closeTime/tradeCount 各类型错误
- toInt64:float64/int64/int/未知 4 个分支
- mapLongShort:global_account 取 LongAccount/ShortAccount,
  top_trader_position 取 LongPosition/ShortPosition,空输入
- mapTicker/mapPremiumIndex/mapFundingHistory/mapOpenInterest/
  mapOpenInterestHistory/mapTakerVolume:纯字段平移,单 case 验证

mapper.go 9 函数全部 100%。包级 31.6% 是 client.go/market.go/
derivatives.go 走真实 HTTP 拉低;这部分留 v2.1 集成测试再补。
This commit is contained in:
dela
2026-05-24 20:35:39 +08:00
parent 0dabdb29ac
commit c478a5feb9

View File

@@ -0,0 +1,231 @@
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)
}