You've already forked agentic-coding-workflow
121 lines
2.8 KiB
Go
121 lines
2.8 KiB
Go
package notify
|
|
|
|
import (
|
|
"context"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/google/uuid"
|
|
)
|
|
|
|
func TestStreamHubSubscribeReceivesPublish(t *testing.T) {
|
|
hub := NewStreamHub()
|
|
uid := uuid.New()
|
|
ch, cancel := hub.Subscribe(uid)
|
|
defer cancel()
|
|
|
|
want := Message{ID: uuid.New(), UserID: uid, Topic: "t", Title: "hi"}
|
|
hub.Publish(uid, want)
|
|
|
|
select {
|
|
case got := <-ch:
|
|
if got.ID != want.ID {
|
|
t.Fatalf("got %v, want %v", got.ID, want.ID)
|
|
}
|
|
case <-time.After(time.Second):
|
|
t.Fatal("did not receive published message")
|
|
}
|
|
}
|
|
|
|
func TestStreamHubUnsubscribeStopsDelivery(t *testing.T) {
|
|
hub := NewStreamHub()
|
|
uid := uuid.New()
|
|
ch, cancel := hub.Subscribe(uid)
|
|
cancel() // immediate unsubscribe closes the channel
|
|
|
|
// channel should be closed, draining returns zero value + !ok.
|
|
if _, ok := <-ch; ok {
|
|
t.Fatal("expected channel closed after cancel")
|
|
}
|
|
// Publishing to an unsubscribed user must not panic.
|
|
hub.Publish(uid, Message{UserID: uid})
|
|
}
|
|
|
|
func TestStreamHubDropsSlowSubscriber(t *testing.T) {
|
|
hub := NewStreamHub()
|
|
uid := uuid.New()
|
|
ch, cancel := hub.Subscribe(uid)
|
|
defer cancel()
|
|
|
|
// Flood beyond buffer; Publish must not block / deadlock.
|
|
done := make(chan struct{})
|
|
go func() {
|
|
for i := 0; i < streamSubBuffer*4; i++ {
|
|
hub.Publish(uid, Message{UserID: uid, Topic: "flood"})
|
|
}
|
|
close(done)
|
|
}()
|
|
select {
|
|
case <-done:
|
|
case <-time.After(2 * time.Second):
|
|
t.Fatal("Publish deadlocked on slow subscriber")
|
|
}
|
|
// Buffer holds at most streamSubBuffer; the rest were dropped.
|
|
count := 0
|
|
for {
|
|
select {
|
|
case <-ch:
|
|
count++
|
|
default:
|
|
if count > streamSubBuffer {
|
|
t.Fatalf("buffered %d > cap %d (drop-on-slow violated)", count, streamSubBuffer)
|
|
}
|
|
return
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestDispatcherPublishesAfterInsert(t *testing.T) {
|
|
repo := &fakeRepo{}
|
|
d := NewDispatcher(repo, newTestLogger())
|
|
hub := NewStreamHub()
|
|
d.SetStreamHub(hub)
|
|
|
|
uid := uuid.New()
|
|
ch, cancel := hub.Subscribe(uid)
|
|
defer cancel()
|
|
|
|
if err := d.Dispatch(context.Background(), Message{UserID: uid, Topic: "x", Title: "y"}); err != nil {
|
|
t.Fatalf("dispatch: %v", err)
|
|
}
|
|
select {
|
|
case got := <-ch:
|
|
if got.Topic != "x" {
|
|
t.Fatalf("topic = %q", got.Topic)
|
|
}
|
|
case <-time.After(time.Second):
|
|
t.Fatal("hub did not receive dispatched message")
|
|
}
|
|
}
|
|
|
|
func TestDispatcherNoPublishOnInsertFailure(t *testing.T) {
|
|
repo := &fakeRepo{failNext: true}
|
|
d := NewDispatcher(repo, newTestLogger())
|
|
hub := NewStreamHub()
|
|
d.SetStreamHub(hub)
|
|
|
|
uid := uuid.New()
|
|
ch, cancel := hub.Subscribe(uid)
|
|
defer cancel()
|
|
|
|
if err := d.Dispatch(context.Background(), Message{UserID: uid}); err == nil {
|
|
t.Fatal("expected dispatch error on insert failure")
|
|
}
|
|
select {
|
|
case <-ch:
|
|
t.Fatal("hub received message despite insert failure")
|
|
case <-time.After(100 * time.Millisecond):
|
|
// expected: no publish.
|
|
}
|
|
}
|