Files
aiaa-notification-server/internal/subscriber/handle_test.go
T
ryan 6f846a0a3c feat: subscribe to RabbitMQ trade signals and notify by rules
Consume configurable queues, format signals (including period), share NotifyService with HTTP, and drop duplicate bodies within 1h.
2026-08-15 17:34:49 +08:00

136 lines
4.2 KiB
Go

package subscriber
import (
"context"
"errors"
"strings"
"testing"
"aiaa-notification-service/internal/model"
"aiaa-notification-service/internal/notify"
"aiaa-notification-service/internal/subscriber/tradesignal"
)
func TestDecideRetry(t *testing.T) {
if DecideRetry(0, 3) != DispositionRetry {
t.Fatal("first failure should retry")
}
if DecideRetry(3, 3) != DispositionDLQ {
t.Fatal("retry 4 > 3 should dlq")
}
}
func TestRetryCount(t *testing.T) {
if RetryCount(nil) != 0 {
t.Fatal()
}
if RetryCount(map[string]any{"x-retry-count": int32(2)}) != 2 {
t.Fatal()
}
}
func enabledSrc() *model.Source {
return &model.Source{ID: 1, Name: "trade-signal", Status: 1}
}
func TestHandleInvalidJSONAck(t *testing.T) {
d := HandleMessage(context.Background(), HandleInput{Body: []byte(`{`), SourceName: "trade-signal", MaxRetry: 3},
tradesignal.NewConverter(nil),
func(context.Context, string) (*model.Source, error) { return enabledSrc(), nil },
func(context.Context, notify.Request) (notify.Result, error) {
t.Fatal("process should not run")
return notify.Result{}, nil
})
if d != DispositionAck {
t.Fatalf("%v", d)
}
}
func TestHandleMissingSourceAck(t *testing.T) {
d := HandleMessage(context.Background(), HandleInput{
Body: []byte(`{"action":"OPEN","symbol":"BTCUSDT"}`), SourceName: "trade-signal", MaxRetry: 3,
}, tradesignal.NewConverter(nil),
func(context.Context, string) (*model.Source, error) { return nil, errors.New("not found") },
func(context.Context, notify.Request) (notify.Result, error) {
t.Fatal("process")
return notify.Result{}, nil
})
if d != DispositionAck {
t.Fatalf("%v", d)
}
}
func TestHandleDisabledSourceAck(t *testing.T) {
d := HandleMessage(context.Background(), HandleInput{
Body: []byte(`{"action":"OPEN"}`), SourceName: "trade-signal", MaxRetry: 3,
}, tradesignal.NewConverter(nil),
func(context.Context, string) (*model.Source, error) {
return &model.Source{ID: 1, Name: "trade-signal", Status: 0}, nil
},
func(context.Context, notify.Request) (notify.Result, error) {
t.Fatal("process")
return notify.Result{}, nil
})
if d != DispositionAck {
t.Fatalf("%v", d)
}
}
func TestHandleProcessUnprocessableAck(t *testing.T) {
d := HandleMessage(context.Background(), HandleInput{
Body: []byte(`{"action":"OPEN"}`), SourceName: "trade-signal", MaxRetry: 3,
}, tradesignal.NewConverter(nil),
func(context.Context, string) (*model.Source, error) { return enabledSrc(), nil },
func(context.Context, notify.Request) (notify.Result, error) {
return notify.Result{}, notify.ErrUnprocessable
})
if d != DispositionAck {
t.Fatalf("%v", d)
}
}
func TestHandleProcessErrorRetryThenDLQ(t *testing.T) {
process := func(context.Context, notify.Request) (notify.Result, error) {
return notify.Result{}, errors.New("db down")
}
lookup := func(context.Context, string) (*model.Source, error) { return enabledSrc(), nil }
conv := tradesignal.NewConverter(nil)
body := []byte(`{"action":"OPEN"}`)
d := HandleMessage(context.Background(), HandleInput{Body: body, SourceName: "s", MaxRetry: 3}, conv, lookup, process)
if d != DispositionRetry {
t.Fatalf("%v", d)
}
d = HandleMessage(context.Background(), HandleInput{
Body: body, Headers: map[string]any{"x-retry-count": 3}, SourceName: "s", MaxRetry: 3,
}, conv, lookup, process)
if d != DispositionDLQ {
t.Fatalf("%v", d)
}
}
func TestHandleSuccessAckPassesEventAndFormatted(t *testing.T) {
var got notify.Request
d := HandleMessage(context.Background(), HandleInput{
Body: []byte(`{"action":"CLOSE","symbol":"ETHUSDT","period":"4h","price":1}`),
SourceName: "trade-signal", MaxRetry: 3,
}, tradesignal.NewConverter(nil),
func(context.Context, string) (*model.Source, error) { return enabledSrc(), nil },
func(_ context.Context, req notify.Request) (notify.Result, error) {
got = req
return notify.Result{Matched: true, Channels: []string{"dingtalk:1"}}, nil
})
if d != DispositionAck {
t.Fatalf("%v", d)
}
if got.Event != "trade.close" {
t.Fatalf("event=%q", got.Event)
}
if got.Data["period"] != "4h" {
t.Fatalf("period=%v", got.Data["period"])
}
formatted, _ := got.Data["formatted"].(string)
if !strings.Contains(formatted, "周期: 4h") {
t.Fatalf("formatted=%s", formatted)
}
}