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) } }