111 lines
3.4 KiB
Go
111 lines
3.4 KiB
Go
package services
|
|
|
|
import (
|
|
"sync"
|
|
"testing"
|
|
|
|
"git.saqut.com/saqut/mwse/internal/notify"
|
|
"git.saqut.com/saqut/mwse/internal/ws"
|
|
)
|
|
|
|
// recTrigger records suit replies pushed outward (the #44 3rd-party trigger).
|
|
type recTrigger struct {
|
|
mu sync.Mutex
|
|
got []notify.Notification
|
|
}
|
|
|
|
func (r *recTrigger) NotifyReplied(n notify.Notification) {
|
|
r.mu.Lock()
|
|
r.got = append(r.got, n)
|
|
r.mu.Unlock()
|
|
}
|
|
|
|
func (r *recTrigger) count() int {
|
|
r.mu.Lock()
|
|
defer r.mu.Unlock()
|
|
return len(r.got)
|
|
}
|
|
|
|
// TestNotifyOfflineThenDeliverOnConnect is the #43 store-and-forward core: a
|
|
// message left for an offline client is delivered when that client connects.
|
|
func TestNotifyOfflineThenDeliverOnConnect(t *testing.T) {
|
|
hub := newHub()
|
|
a, _ := connect(hub, "alice")
|
|
|
|
res := asMap(t, hub.Handle(a, msg("notify/send", "to", "bob", "pack", map[string]any{"text": "hi"})))
|
|
if res["status"] != "success" {
|
|
t.Fatalf("notify/send = %v", res)
|
|
}
|
|
trace, _ := res["trace"].(string)
|
|
if trace == "" {
|
|
t.Fatal("notify/send should return a trace id")
|
|
}
|
|
|
|
// bob was offline; now connects and must receive the queued notification.
|
|
_, fb := connect(hub, "bob")
|
|
sig := waitSignal(t, fb, "notify")
|
|
if sig["trace"] != trace {
|
|
t.Fatalf("delivered trace = %v, want %s", sig["trace"], trace)
|
|
}
|
|
if p, _ := sig["pack"].(map[string]any); p["text"] != "hi" {
|
|
t.Fatalf("delivered pack = %v", sig["pack"])
|
|
}
|
|
|
|
// Status reports delivered.
|
|
st := asMap(t, hub.Handle(a, msg("notify/status", "trace", trace)))
|
|
if st["delivered"] != true {
|
|
t.Fatalf("status = %v, want delivered", st)
|
|
}
|
|
}
|
|
|
|
// TestNotifyImmediateWhenOnline delivers without waiting when the target is up.
|
|
func TestNotifyImmediateWhenOnline(t *testing.T) {
|
|
hub := newHub()
|
|
a, _ := connect(hub, "alice")
|
|
_, fb := connect(hub, "bob")
|
|
|
|
hub.Handle(a, msg("notify/send", "to", "bob", "pack", map[string]any{"text": "now"}))
|
|
sig := waitSignal(t, fb, "notify")
|
|
if p, _ := sig["pack"].(map[string]any); p["text"] != "now" {
|
|
t.Fatalf("immediate delivery pack = %v", sig["pack"])
|
|
}
|
|
}
|
|
|
|
// TestNotifySuitReply is the #44 reply path: a suit notification's reply reaches
|
|
// the 3rd-party trigger and is signalled back to the origin client.
|
|
func TestNotifySuitReply(t *testing.T) {
|
|
trig := &recTrigger{}
|
|
hub := ws.NewHub()
|
|
Register(hub, WithNotifyTrigger(trig))
|
|
|
|
a, fa := connect(hub, "alice")
|
|
b, fb := connect(hub, "bob")
|
|
|
|
res := asMap(t, hub.Handle(a, msg("notify/send", "to", "bob", "suit", true, "pack", map[string]any{"q": "ok?"})))
|
|
trace, _ := res["trace"].(string)
|
|
|
|
sig := waitSignal(t, fb, "notify")
|
|
if sig["suit"] != true {
|
|
t.Fatalf("notify suit flag = %v, want true", sig["suit"])
|
|
}
|
|
|
|
// bob replies to the suit.
|
|
rep := asMap(t, hub.Handle(b, msg("notify/reply", "trace", trace, "pack", map[string]any{"a": "yes"})))
|
|
if rep["status"] != "success" {
|
|
t.Fatalf("notify/reply = %v", rep)
|
|
}
|
|
|
|
// The 3rd-party trigger fired, and the origin (alice) got the reply signal.
|
|
waitFor(t, func() bool { return trig.count() == 1 })
|
|
got := waitSignal(t, fa, "notify/reply")
|
|
if p, _ := got["pack"].(map[string]any); p["a"] != "yes" {
|
|
t.Fatalf("origin reply signal pack = %v", got["pack"])
|
|
}
|
|
|
|
// A non-suit reply must be rejected.
|
|
plain := asMap(t, hub.Handle(a, msg("notify/send", "to", "bob", "pack", map[string]any{})))
|
|
if r := asMap(t, hub.Handle(b, msg("notify/reply", "trace", plain["trace"], "pack", map[string]any{}))); r["status"] != "fail" {
|
|
t.Fatalf("reply to non-suit = %v, want fail", r)
|
|
}
|
|
}
|