package tradesignal import ( "errors" "strings" "testing" "aiaa-notification-service/internal/config" "aiaa-notification-service/internal/engine" ) func TestConvertOpen(t *testing.T) { lev := 100 c := NewConverter(map[string]config.StrategyOverride{ "BLONG": {QuantityMultipliers: config.QuantityMultipliers{Open: 100}, Leverage: &lev}, }) event, data, err := c.Convert([]byte(`{ "signalId":"s1","strategyCode":"BLONG","symbol":"BTCUSDT", "side":"LONG","action":"OPEN","quantity":0.01,"price":64000, "leverage":10,"period":"1h","eventTime":"2026-06-23T01:30:00Z" }`)) if err != nil { t.Fatal(err) } if event != "trade.open" { t.Fatalf("event=%q", event) } formatted, _ := data["formatted"].(string) if !strings.Contains(formatted, "周期: 1h") || !strings.Contains(formatted, "开仓数量: 1.00") { t.Fatalf("formatted=\n%s", formatted) } if data["period"] != "1h" || data["strategyCode"] != "BLONG" { t.Fatalf("data=%v", data) } if data["leverage"] != float64(100) && data["leverage"] != 100 { t.Fatalf("leverage=%v", data["leverage"]) } } // TestOverrideForLowercaseKey verifies overrides still match when viper // lower-cases the strategy_overrides map keys during config load. func TestOverrideForLowercaseKey(t *testing.T) { lev := 100 c := NewConverter(map[string]config.StrategyOverride{ "blong": {QuantityMultipliers: config.QuantityMultipliers{Add: 100}, Leverage: &lev}, }) o := c.overrideFor("BLONG") if o == nil { t.Fatal("overrideFor(BLONG)=nil, lowercase config key should match") } if got := o.QuantityMultiplierFor("ADD"); got != 100 { t.Fatalf("add multiplier=%v want 100", got) } if o.Leverage == nil || *o.Leverage != 100 { t.Fatalf("leverage=%v want 100", o.Leverage) } } func TestConvertKeepsExtraJSONFields(t *testing.T) { _, data, err := NewConverter(nil).Convert([]byte(`{ "action":"OPEN","symbol":"BTCUSDT","price":63014.61, "totalAvgPx":63014.61,"investmentAmount":100095.24 }`)) if err != nil { t.Fatal(err) } if data["totalAvgPx"] != 63014.61 { t.Fatalf("totalAvgPx=%v", data["totalAvgPx"]) } } func TestConvertInvalidJSON(t *testing.T) { _, _, err := NewConverter(nil).Convert([]byte(`{`)) if !errors.Is(err, ErrInvalidSignal) { t.Fatalf("err=%v", err) } } func TestConvertMissingAction(t *testing.T) { _, _, err := NewConverter(nil).Convert([]byte(`{"symbol":"BTCUSDT"}`)) if !errors.Is(err, ErrInvalidSignal) { t.Fatalf("err=%v", err) } } func TestConvertRawMessageWithoutAction(t *testing.T) { event, data, err := NewConverter(nil).Convert([]byte(`{ "strategyCode":"PUTEJJ", "rawMessage":"4.7.15~2026.8.15 本周期 10万本金 期末109640,盈利9.6%" }`)) if err != nil { t.Fatal(err) } if event != "trade.message" { t.Fatalf("event=%q", event) } if data["rawMessage"] != "4.7.15~2026.8.15 本周期 10万本金 期末109640,盈利9.6%" { t.Fatalf("data=%v", data) } } func TestConvertThenRenderCopyTradeTinyPEPE(t *testing.T) { lev := 100 c := NewConverter(map[string]config.StrategyOverride{ "BLONG": {QuantityMultipliers: config.QuantityMultipliers{Open: 100}, Leverage: &lev}, }) _, data, err := c.Convert([]byte(`{ "signalId":"s1","strategyCode":"BLONG","symbol":"PEPEUSDT", "side":"SHORT","action":"OPEN","quantity":10000000,"price":0.00000059, "leverage":10,"eventTime":"2026-08-22T02:04:29Z" }`)) if err != nil { t.Fatal(err) } if p, ok := data["price"].(float64); !ok || p != 0.00000059 { t.Fatalf("convert price=%v (%T), want float64 0.00000059", data["price"], data["price"]) } data["pushedAt"] = "2026.08.22 10:04:29" const tmpl = "{{case .side \"LONG\" \"多单\" \"SHORT\" \"空单\"}}{{case .action \"OPEN\" \"开仓\" \"ADD\" \"加仓\" \"CLOSE\" \"平仓\" \"REDUCE\" \"减仓\"}}\n交易品种: {{case .symbol \"ETHUSDT\" \"ETH\" \"BTCUSDT\" \"BTC\" \"SOLUSDT\" \"SOL\" \"BNBUSDT\" \"BNB\" .symbol}}\n{{case .action \"OPEN\" \"开仓价格\" \"ADD\" \"加仓价格\" \"CLOSE\" \"平仓价格\" \"REDUCE\" \"减仓价格\"}}: {{printf \"%.2f\" .price}}\n{{case .action \"OPEN\" \"开仓数量\" \"ADD\" \"加仓数量\" \"CLOSE\" \"平仓数量\" \"REDUCE\" \"减仓数量\"}}: {{printf \"%.2f\" .quantity}}\n平均单价: {{printf \"%.2f\" .avgPrice}}\n{{if or (eq .action \"OPEN\") (eq .action \"ADD\")}}{{if .leverage}}杠杆: {{.leverage}}x\n{{end}}{{end}}策略: {{case .strategyCode \"BLONG\" \"B龙策略\" .strategyCode}}\n推送时间: {{.pushedAt}}" out, err := engine.NewRenderer().Render(tmpl, data) if err != nil { t.Fatal(err) } for _, want := range []string{ "空单开仓", "交易品种: PEPEUSDT", "开仓价格: 0.00000059", "开仓数量: 1000000000.00", "平均单价: 0.00000059", "杠杆: 100x", } { if !strings.Contains(out, want) { t.Errorf("missing %q in\n%s", want, out) } } if strings.Contains(out, "开仓价格: 0.00\n") || strings.Contains(out, "平均单价: 0.00\n") { t.Errorf("tiny price collapsed to 0.00:\n%s", out) } }