Files
aiaa-notification-server/internal/engine/renderer_test.go
T
ryan f7ea2e7cf9 feat(crypto策略): 支持巴菲特策略纯 rawMessage 信号推送
Motivation:
新增巴菲特激进/稳健策略(PUTEJJ/PUTEWJ)的跟单推送能力。该类信号仅有 rawMessage、无 action 字段,需按原文直接推送,并过滤启动文案、本地化时间字段。

Changes:

* 支持无 action 的纯 rawMessage 信号,映射为 trade.message 事件
* 新增 not_contains 条件操作符,用于过滤启动文案
* 新增 replace 模板函数,将 Time: 替换为推送时间:
* 补充巴菲特策略模板、规则与渠道配置文档
2026-08-17 16:38:09 +08:00

193 lines
6.4 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package engine
import (
"strings"
"testing"
)
func TestRenderer(t *testing.T) {
r := NewRenderer()
tmpl := "🚀 {{.symbol}} 开仓通知\n价格: {{.price}}"
data := map[string]interface{}{"symbol": "BTC", "price": 65000}
result, err := r.Render(tmpl, data)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if !strings.Contains(result, "BTC") || !strings.Contains(result, "65000") {
t.Errorf("unexpected output: %s", result)
}
}
func TestRendererMissingKeyEmpty(t *testing.T) {
r := NewRenderer()
_, err := r.Render("x{{.nonexistent}}y", map[string]interface{}{})
if err != nil {
t.Fatal(err)
}
}
func TestRendererLineOmitsEmpty(t *testing.T) {
r := NewRenderer()
tmpl := `{{line "币种" .symbol}}{{line "平均价" .totalAvgPx}}{{line "价格" .price}}{{line "止损价" .stopLossPrice}}`
out, err := r.Render(tmpl, map[string]interface{}{
"symbol": "ICP",
"price": 2.273,
"totalAvgPx": "",
"stopLossPrice": 0,
})
if err != nil {
t.Fatal(err)
}
if !strings.Contains(out, "币种:ICP") || !strings.Contains(out, "价格:2.273") {
t.Fatalf("out=%q", out)
}
if strings.Contains(out, "平均价") || strings.Contains(out, "止损价") {
t.Fatalf("empty lines should be omitted, out=%q", out)
}
}
func TestRendererLineLabelAlreadyHasColon(t *testing.T) {
r := NewRenderer()
out, err := r.Render(`{{line "平均价:" .totalAvgPx}}`, map[string]interface{}{"totalAvgPx": 1029})
if err != nil {
t.Fatal(err)
}
if strings.Count(out, "") != 1 || !strings.Contains(out, "平均价:1029") {
t.Fatalf("out=%q", out)
}
}
func TestRendererLineMissingKey(t *testing.T) {
r := NewRenderer()
out, err := r.Render(`{{line "平均价" .totalAvgPx}}{{line "币种" .symbol}}`, map[string]interface{}{"symbol": "ICP"})
if err != nil {
t.Fatal(err)
}
if strings.Contains(out, "平均价") || !strings.Contains(out, "币种:ICP") {
t.Fatalf("out=%q", out)
}
}
func TestRendererCaseSwitch(t *testing.T) {
r := NewRenderer()
tmpl := `{{case .action "OPEN" "开仓" "CLOSE" "平仓" "GAIN" "止盈"}}`
out, err := r.Render(tmpl, map[string]interface{}{"action": "CLOSE"})
if err != nil {
t.Fatal(err)
}
if out != "平仓" {
t.Fatalf("action CLOSE: %q", out)
}
out, err = r.Render(tmpl, map[string]interface{}{"action": "open"})
if err != nil {
t.Fatal(err)
}
if out != "开仓" {
t.Fatalf("action open: %q", out)
}
}
func TestRendererCaseEventSuffix(t *testing.T) {
r := NewRenderer()
tmpl := `{{case .event ".open" "开仓" ".close" "平仓"}}`
out, err := r.Render(tmpl, map[string]interface{}{"event": "trade.close"})
if err != nil {
t.Fatal(err)
}
if out != "平仓" {
t.Fatalf("event trade.close: %q", out)
}
}
func TestRendererReplace(t *testing.T) {
r := NewRenderer()
out, err := r.Render(`{{replace .rawMessage "Time:" "推送时间:"}}`, map[string]interface{}{
"rawMessage": "激进版AI 1.0\n市价开空\nTime: 2026.08.17 14:46:04",
})
if err != nil {
t.Fatal(err)
}
if !strings.Contains(out, "推送时间: 2026.08.17 14:46:04") || strings.Contains(out, "Time:") {
t.Fatalf("out=%q", out)
}
}
func TestRendererCaseDefault(t *testing.T) {
r := NewRenderer()
out, err := r.Render(`{{case .action "OPEN" "开仓" "未知"}}`, map[string]interface{}{"action": "HOLD"})
if err != nil {
t.Fatal(err)
}
if out != "未知" {
t.Fatalf("got %q", out)
}
}
func TestCopyTradeTemplate(t *testing.T) {
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}}"
r := NewRenderer()
cases := []struct {
name string
data map[string]interface{}
want []string
not []string
}{
{
name: "open",
data: map[string]interface{}{
"side": "SHORT", "action": "OPEN", "symbol": "ETHUSDT",
"price": 1898.76, "quantity": 2.0, "avgPrice": 1898.76,
"leverage": 100, "strategyCode": "BLONG", "pushedAt": "2026.08.13 13:15:42",
},
want: []string{"空单开仓", "交易品种: ETH", "开仓价格: 1898.76", "开仓数量: 2.00", "平均单价: 1898.76", "杠杆: 100x", "策略: B龙策略", "推送时间: 2026.08.13 13:15:42"},
},
{
name: "close",
data: map[string]interface{}{
"side": "SHORT", "action": "CLOSE", "symbol": "ETHUSDT",
"price": 1883.35, "quantity": 2.0, "avgPrice": 1898.76,
"leverage": 100, "strategyCode": "BLONG", "pushedAt": "2026.08.13 17:14:31",
},
want: []string{"空单平仓", "平仓价格: 1883.35", "平仓数量: 2.00", "策略: B龙策略", "推送时间: 2026.08.13 17:14:31"},
not: []string{"杠杆:"},
},
{
name: "reduce",
data: map[string]interface{}{
"side": "SHORT", "action": "REDUCE", "symbol": "ETHUSDT",
"price": 1889.43, "quantity": 3.1, "avgPrice": 1899.03,
"strategyCode": "BLONG", "pushedAt": "2026.08.12 22:05:02",
},
want: []string{"空单减仓", "减仓价格: 1889.43", "减仓数量: 3.10", "平均单价: 1899.03", "策略: B龙策略"},
not: []string{"杠杆:"},
},
{
name: "add",
data: map[string]interface{}{
"side": "SHORT", "action": "ADD", "symbol": "ETHUSDT",
"price": 1933.05, "quantity": 3.8, "avgPrice": 1933.05,
"leverage": 100, "strategyCode": "BLONG", "pushedAt": "2026.08.10 06:14:28",
},
want: []string{"空单加仓", "加仓价格: 1933.05", "加仓数量: 3.80", "杠杆: 100x", "策略: B龙策略", "推送时间: 2026.08.10 06:14:28"},
},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
out, err := r.Render(tmpl, tc.data)
if err != nil {
t.Fatal(err)
}
t.Logf("\n%s", out)
for _, w := range tc.want {
if !strings.Contains(out, w) {
t.Errorf("missing %q in\n%s", w, out)
}
}
for _, n := range tc.not {
if strings.Contains(out, n) {
t.Errorf("unexpected %q in\n%s", n, out)
}
}
})
}
}