6f846a0a3c
Consume configurable queues, format signals (including period), share NotifyService with HTTP, and drop duplicate bodies within 1h.
90 lines
2.8 KiB
Go
90 lines
2.8 KiB
Go
package subscriber
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"sync/atomic"
|
|
"testing"
|
|
|
|
"aiaa-notification-service/internal/model"
|
|
"aiaa-notification-service/internal/notify"
|
|
"aiaa-notification-service/internal/subscriber/tradesignal"
|
|
)
|
|
|
|
func TestMessageHashStable(t *testing.T) {
|
|
a := MessageHash([]byte(`{"action":"OPEN","signalId":"s1"}`))
|
|
b := MessageHash([]byte(`{"action":"OPEN","signalId":"s1"}`))
|
|
c := MessageHash([]byte(`{"action":"CLOSE","signalId":"s1"}`))
|
|
if a == "" || a != b {
|
|
t.Fatalf("hash should be stable, a=%q b=%q", a, b)
|
|
}
|
|
if a == c {
|
|
t.Fatal("different bodies should hash differently")
|
|
}
|
|
}
|
|
|
|
func TestMemoryDeduperClaimOnce(t *testing.T) {
|
|
d := NewMemoryDeduper()
|
|
ok, err := d.Claim(context.Background(), "abc")
|
|
if err != nil || !ok {
|
|
t.Fatalf("first claim ok=%v err=%v", ok, err)
|
|
}
|
|
ok, err = d.Claim(context.Background(), "abc")
|
|
if err != nil || ok {
|
|
t.Fatalf("second claim should miss, ok=%v err=%v", ok, err)
|
|
}
|
|
if err := d.Release(context.Background(), "abc"); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
ok, err = d.Claim(context.Background(), "abc")
|
|
if err != nil || !ok {
|
|
t.Fatalf("after release should claim, ok=%v err=%v", ok, err)
|
|
}
|
|
}
|
|
|
|
func TestHandleDuplicateAckSkipsProcess(t *testing.T) {
|
|
dedup := NewMemoryDeduper()
|
|
var n atomic.Int32
|
|
process := func(context.Context, notify.Request) (notify.Result, error) {
|
|
n.Add(1)
|
|
return notify.Result{Matched: true}, nil
|
|
}
|
|
lookup := func(context.Context, string) (*model.Source, error) { return enabledSrc(), nil }
|
|
conv := tradesignal.NewConverter(nil)
|
|
body := []byte(`{"action":"OPEN","signalId":"dup-1"}`)
|
|
in := HandleInput{Body: body, SourceName: "trade-signal", MaxRetry: 3, Deduper: dedup}
|
|
|
|
if d := HandleMessage(context.Background(), in, conv, lookup, process); d != DispositionAck {
|
|
t.Fatalf("first=%v", d)
|
|
}
|
|
if d := HandleMessage(context.Background(), in, conv, lookup, process); d != DispositionAck {
|
|
t.Fatalf("dup=%v", d)
|
|
}
|
|
if n.Load() != 1 {
|
|
t.Fatalf("process called %d times, want 1", n.Load())
|
|
}
|
|
}
|
|
|
|
func TestHandleProcessErrorReleasesDedup(t *testing.T) {
|
|
dedup := NewMemoryDeduper()
|
|
var n atomic.Int32
|
|
process := func(context.Context, notify.Request) (notify.Result, error) {
|
|
n.Add(1)
|
|
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","signalId":"retry-1"}`)
|
|
in := HandleInput{Body: body, SourceName: "s", MaxRetry: 3, Deduper: dedup}
|
|
|
|
if d := HandleMessage(context.Background(), in, conv, lookup, process); d != DispositionRetry {
|
|
t.Fatalf("first=%v", d)
|
|
}
|
|
if d := HandleMessage(context.Background(), in, conv, lookup, process); d != DispositionRetry {
|
|
t.Fatalf("retry should process again, got %v", d)
|
|
}
|
|
if n.Load() != 2 {
|
|
t.Fatalf("process called %d times, want 2", n.Load())
|
|
}
|
|
}
|