Files
aiaa-notification-server/internal/engine/renderer_test.go
T
ryan 33b2a96bcf fix(消息去重): 去重键加入来源维度,避免跨来源信号被误去重
Motivation:
不同来源(如 crypto-strategy 与 trade-signal)可能产生策略、币种、周期、方向、价格完全一致的信号,旧去重键仅按这五维计算,导致后到的不同来源信号被误判为重复而漏推,无法支撑跟单策略等新增来源并行推送。

Changes:

* 信号去重键新增来源(source)维度,不同来源的相同信号不再互相去重
* 信号哈希计算时透传来源名称,同一来源内的重复消息仍正常去重
* 补充跟单策略文案模板的渲染测试与接口创建文档
2026-08-17 15:39:20 +08:00

180 lines
6.0 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 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)
}
}
})
}
}