- Update the Subscribe and SubscribeFrom methods to return a cancel function, ensuring proper resource cleanup when subscriptions are no longer needed. - Modify unsubscribe logic to close channels safely, preventing potential panics from sending on closed channels. - Enhance test cases to utilize the new cancel functionality, ensuring robust handling of subscriptions in various scenarios.
185 lines
4.6 KiB
Go
185 lines
4.6 KiB
Go
package event_test
|
|
|
|
import (
|
|
"context"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/yaoapp/yao/event"
|
|
"github.com/yaoapp/yao/event/types"
|
|
)
|
|
|
|
// --- Phase 7: Subscriber tests ---
|
|
|
|
func TestSubscribe_Basic(t *testing.T) {
|
|
event.Reset()
|
|
defer event.Reset()
|
|
|
|
event.Register("foo", &recordHandler{})
|
|
_ = event.Start()
|
|
defer func() { _ = event.Stop(context.Background()) }()
|
|
|
|
ch := make(chan *types.Event, 10)
|
|
subID := event.Subscribe("foo.*", ch)
|
|
if subID == "" {
|
|
t.Fatal("expected non-empty subscription ID")
|
|
}
|
|
defer event.Unsubscribe(subID)
|
|
|
|
_, _ = event.Push(context.Background(), "foo.bar", "payload")
|
|
_, _ = event.Push(context.Background(), "foo.baz", "payload2")
|
|
|
|
received := drainChan(ch, 2, 200*time.Millisecond)
|
|
if len(received) != 2 {
|
|
t.Fatalf("expected 2 events, got %d", len(received))
|
|
}
|
|
if received[0].Type != "foo.bar" {
|
|
t.Fatalf("expected foo.bar, got %s", received[0].Type)
|
|
}
|
|
if received[1].Type != "foo.baz" {
|
|
t.Fatalf("expected foo.baz, got %s", received[1].Type)
|
|
}
|
|
}
|
|
|
|
func TestSubscribe_PatternFilter(t *testing.T) {
|
|
event.Reset()
|
|
defer event.Reset()
|
|
|
|
event.Register("foo", &recordHandler{})
|
|
event.Register("bar", &recordHandler{})
|
|
_ = event.Start()
|
|
defer func() { _ = event.Stop(context.Background()) }()
|
|
|
|
ch := make(chan *types.Event, 10)
|
|
subID := event.Subscribe("foo.*", ch, event.Filter(func(ev *types.Event) bool {
|
|
return ev.Type == "foo.keep"
|
|
}))
|
|
defer event.Unsubscribe(subID)
|
|
|
|
_, _ = event.Push(context.Background(), "foo.keep", nil)
|
|
_, _ = event.Push(context.Background(), "foo.drop", nil)
|
|
_, _ = event.Push(context.Background(), "bar.thing", nil)
|
|
|
|
received := drainChan(ch, 1, 200*time.Millisecond)
|
|
if len(received) != 1 {
|
|
t.Fatalf("expected 1 filtered event, got %d", len(received))
|
|
}
|
|
if received[0].Type != "foo.keep" {
|
|
t.Fatalf("expected foo.keep, got %s", received[0].Type)
|
|
}
|
|
}
|
|
|
|
func TestSubscribe_Unsubscribe(t *testing.T) {
|
|
event.Reset()
|
|
defer event.Reset()
|
|
|
|
event.Register("foo", &recordHandler{})
|
|
_ = event.Start()
|
|
defer func() { _ = event.Stop(context.Background()) }()
|
|
|
|
ch := make(chan *types.Event, 10)
|
|
subID := event.Subscribe("foo.*", ch)
|
|
|
|
_, _ = event.Push(context.Background(), "foo.first", nil)
|
|
time.Sleep(50 * time.Millisecond)
|
|
|
|
event.Unsubscribe(subID)
|
|
|
|
_, _ = event.Push(context.Background(), "foo.second", nil)
|
|
time.Sleep(50 * time.Millisecond)
|
|
|
|
received := drainChan(ch, 10, 100*time.Millisecond)
|
|
for _, ev := range received {
|
|
if ev.Type == "foo.second" {
|
|
t.Fatal("should not receive events after Unsubscribe")
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestSubscribe_ChanFull_Skip(t *testing.T) {
|
|
event.Reset()
|
|
defer event.Reset()
|
|
|
|
event.Register("foo", &recordHandler{})
|
|
_ = event.Start()
|
|
defer func() { _ = event.Stop(context.Background()) }()
|
|
|
|
ch := make(chan *types.Event, 1) // tiny buffer
|
|
subID := event.Subscribe("foo.*", ch)
|
|
defer event.Unsubscribe(subID)
|
|
|
|
// Push multiple events quickly; only 1 should fit in buffer
|
|
for i := 0; i < 5; i++ {
|
|
_, _ = event.Push(context.Background(), "foo.item", i)
|
|
}
|
|
|
|
time.Sleep(100 * time.Millisecond)
|
|
|
|
// Should have at most 1 in channel (rest skipped)
|
|
count := len(ch)
|
|
if count > 1 {
|
|
t.Fatalf("expected at most 1 buffered event, got %d", count)
|
|
}
|
|
}
|
|
|
|
func TestSubscribe_WildcardAll(t *testing.T) {
|
|
event.Reset()
|
|
defer event.Reset()
|
|
|
|
event.Register("foo", &recordHandler{})
|
|
event.Register("bar", &recordHandler{})
|
|
_ = event.Start()
|
|
defer func() { _ = event.Stop(context.Background()) }()
|
|
|
|
ch := make(chan *types.Event, 10)
|
|
subID := event.Subscribe("*", ch)
|
|
defer event.Unsubscribe(subID)
|
|
|
|
_, _ = event.Push(context.Background(), "foo.one", nil)
|
|
_, _ = event.Push(context.Background(), "bar.two", nil)
|
|
|
|
received := drainChan(ch, 2, 200*time.Millisecond)
|
|
if len(received) != 2 {
|
|
t.Fatalf("wildcard * should receive all events, got %d", len(received))
|
|
}
|
|
}
|
|
|
|
func TestSubscribe_StopClearsSubscribers(t *testing.T) {
|
|
event.Reset()
|
|
defer event.Reset()
|
|
|
|
event.Register("foo", &recordHandler{})
|
|
_ = event.Start()
|
|
|
|
ch := make(chan *types.Event, 10)
|
|
_ = event.Subscribe("foo.*", ch)
|
|
|
|
_ = event.Stop(context.Background())
|
|
|
|
// After Stop, Push should fail
|
|
_, err := event.Push(context.Background(), "foo.bar", nil)
|
|
if err != event.ErrNotStarted {
|
|
t.Fatalf("expected ErrNotStarted after Stop, got %v", err)
|
|
}
|
|
}
|
|
|
|
// drainChan reads up to n events from ch within timeout.
|
|
// Stops early if the channel is closed.
|
|
func drainChan(ch chan *types.Event, n int, timeout time.Duration) []*types.Event {
|
|
var result []*types.Event
|
|
timer := time.NewTimer(timeout)
|
|
defer timer.Stop()
|
|
|
|
for range n {
|
|
select {
|
|
case ev, ok := <-ch:
|
|
if !ok {
|
|
return result
|
|
}
|
|
result = append(result, ev)
|
|
case <-timer.C:
|
|
return result
|
|
}
|
|
}
|
|
return result
|
|
}
|