2524340759
- app.md - backend/dist/assets/index-D_Kep7Fb.js - backend/dist/index.html - backend/handlers.go - backend/main.go - frontend/src/App.tsx - frontend/src/api.ts - frontend/src/components/CardChatPanel.tsx - frontend/src/components/LoginPage.tsx - frontend/src/types.ts - ... Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
147 lines
3.4 KiB
Go
147 lines
3.4 KiB
Go
package main
|
|
|
|
import (
|
|
"sync"
|
|
"sync/atomic"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
func TestEventHub_BroadcastToAllUsers(t *testing.T) {
|
|
hub := NewEventHub()
|
|
chA := hub.SubscribeUser("alice")
|
|
chB := hub.SubscribeUser("bob")
|
|
defer hub.UnsubscribeUser("alice", chA)
|
|
defer hub.UnsubscribeUser("bob", chB)
|
|
|
|
hub.PublishJSON("card.updated", "c1", "", map[string]string{"id": "c1"})
|
|
|
|
for _, ch := range []chan Event{chA, chB} {
|
|
select {
|
|
case ev := <-ch:
|
|
if ev.Type != "card.updated" {
|
|
t.Fatalf("type = %q, want card.updated", ev.Type)
|
|
}
|
|
case <-time.After(time.Second):
|
|
t.Fatal("timeout waiting for event")
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestEventHub_PrivateUserEvent(t *testing.T) {
|
|
hub := NewEventHub()
|
|
chA := hub.SubscribeUser("alice")
|
|
chB := hub.SubscribeUser("bob")
|
|
defer hub.UnsubscribeUser("alice", chA)
|
|
defer hub.UnsubscribeUser("bob", chB)
|
|
|
|
hub.PublishJSON("notification.created", "", "alice", map[string]string{"foo": "bar"})
|
|
|
|
select {
|
|
case ev := <-chA:
|
|
if ev.UserID != "alice" {
|
|
t.Fatalf("user_id = %q, want alice", ev.UserID)
|
|
}
|
|
case <-time.After(time.Second):
|
|
t.Fatal("alice did not get private event")
|
|
}
|
|
|
|
select {
|
|
case ev := <-chB:
|
|
t.Fatalf("bob received private event for alice: %+v", ev)
|
|
case <-time.After(100 * time.Millisecond):
|
|
// expected
|
|
}
|
|
}
|
|
|
|
func TestEventHub_CardSubscription(t *testing.T) {
|
|
hub := NewEventHub()
|
|
ch := hub.SubscribeCard("card-1")
|
|
defer hub.UnsubscribeCard("card-1", ch)
|
|
|
|
hub.PublishJSON("message.created", "card-1", "", map[string]string{"id": "m1"})
|
|
hub.PublishJSON("message.created", "card-2", "", map[string]string{"id": "m2"})
|
|
|
|
select {
|
|
case ev := <-ch:
|
|
if ev.CardID != "card-1" {
|
|
t.Fatalf("card_id = %q, want card-1", ev.CardID)
|
|
}
|
|
case <-time.After(time.Second):
|
|
t.Fatal("timeout")
|
|
}
|
|
select {
|
|
case ev := <-ch:
|
|
t.Fatalf("received unexpected event for other card: %+v", ev)
|
|
case <-time.After(100 * time.Millisecond):
|
|
}
|
|
}
|
|
|
|
func TestEventHub_DropPolicyOnSlowConsumer(t *testing.T) {
|
|
hub := NewEventHub()
|
|
ch := hub.SubscribeUser("slow")
|
|
defer hub.UnsubscribeUser("slow", ch)
|
|
|
|
// Fill the buffer + N extra to force drops.
|
|
const extra = 50
|
|
for i := 0; i < eventBufSize+extra; i++ {
|
|
hub.PublishJSON("noise", "", "slow", nil)
|
|
}
|
|
if got := hub.DropCount(); got < extra {
|
|
t.Fatalf("DropCount = %d, want >= %d", got, extra)
|
|
}
|
|
}
|
|
|
|
func TestEventHub_UnsubscribeRemoves(t *testing.T) {
|
|
hub := NewEventHub()
|
|
ch := hub.SubscribeUser("alice")
|
|
hub.UnsubscribeUser("alice", ch)
|
|
// channel must be closed
|
|
select {
|
|
case _, ok := <-ch:
|
|
if ok {
|
|
t.Fatal("expected closed channel")
|
|
}
|
|
default:
|
|
// channel could be drained-and-closed
|
|
}
|
|
// Publish should not panic and should not deliver anywhere.
|
|
hub.PublishJSON("noise", "", "alice", nil)
|
|
}
|
|
|
|
func TestEventHub_ConcurrentPublishers(t *testing.T) {
|
|
hub := NewEventHub()
|
|
ch := hub.SubscribeUser("u")
|
|
defer hub.UnsubscribeUser("u", ch)
|
|
|
|
var received atomic.Uint64
|
|
done := make(chan struct{})
|
|
go func() {
|
|
for range ch {
|
|
received.Add(1)
|
|
}
|
|
close(done)
|
|
}()
|
|
|
|
var wg sync.WaitGroup
|
|
const writers = 10
|
|
const each = 100
|
|
for i := 0; i < writers; i++ {
|
|
wg.Add(1)
|
|
go func() {
|
|
defer wg.Done()
|
|
for j := 0; j < each; j++ {
|
|
hub.PublishJSON("ping", "", "u", nil)
|
|
}
|
|
}()
|
|
}
|
|
wg.Wait()
|
|
// Give the consumer time to drain.
|
|
time.Sleep(200 * time.Millisecond)
|
|
got := received.Load()
|
|
dropped := hub.DropCount()
|
|
if got+dropped < writers*each {
|
|
t.Fatalf("received=%d drop=%d want sum >= %d", got, dropped, writers*each)
|
|
}
|
|
}
|