This repository has been archived on 2026-04-03. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
core/server/events/bus_test.go
Jeffrey Smith 1fbd836c5c Feat v0.7.0 shell contract + surface audit + rebrand
Two-slot shell topbar (home, left, center, bell, user menu) with SDK API
(setLeft/setSlot/setTitle/hide/show). All 4 primary surfaces migrated:
Settings and Team Admin to Pattern B (flat tabs), Admin to Pattern C
(category tabs + sidebar), Docs to Pattern A (default).

Backend WS events: package.changed (broadcast), auth.changed (targeted),
notification.all_read. User menu re-fetches on package/auth changes.
Bell syncs read state across tabs.

Error handling pass with .sw-inline-error CSS primitive. Empty state
guidance for Admin Workflows/Groups. Announcement global dismiss via
localStorage. Rebrand assets deployed (both b/e icon variants, wordmarks,
full icon library). Docs outline scroll-to-heading fix.

Bug fixes: ICD security assertion tightened, workflow-demo error surfacing,
signoff display names, hello-dashboard + team-admin/groups.js deleted.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-01 20:08:57 +00:00

265 lines
7.4 KiB
Go

package events
import (
"encoding/json"
"sync/atomic"
"testing"
"time"
)
func TestMatch(t *testing.T) {
tests := []struct {
label, pattern string
want bool
}{
{"workflow.assigned.abc", "workflow.assigned.abc", true},
{"workflow.assigned.abc", "workflow.assigned.*", true},
{"workflow.assigned.abc", "workflow.*", true},
{"workflow.assigned.abc", "*", true},
{"workflow.assigned.abc", "workflow.assigned.xyz", false},
{"workflow.assigned.abc", "notification.new.*", false},
{"workflow.assigned.abc", "workflow.assigned", false},
{"ping", "ping", true},
{"ping", "pong", false},
{"plugin.hook.pre_completion", "plugin.hook.*", true},
{"plugin.hook.pre_completion", "plugin.*", true},
}
for _, tt := range tests {
got := match(tt.label, tt.pattern)
if got != tt.want {
t.Errorf("match(%q, %q) = %v, want %v", tt.label, tt.pattern, got, tt.want)
}
}
}
func TestBusPublishExact(t *testing.T) {
bus := NewBus()
var count int32
bus.Subscribe("workflow.assigned.abc", func(e Event) {
atomic.AddInt32(&count, 1)
})
bus.Publish(Event{Label: "workflow.assigned.abc", Payload: json.RawMessage(`{}`)})
bus.Publish(Event{Label: "workflow.assigned.xyz", Payload: json.RawMessage(`{}`)})
if atomic.LoadInt32(&count) != 1 {
t.Errorf("expected 1 dispatch, got %d", count)
}
}
func TestBusPublishWildcard(t *testing.T) {
bus := NewBus()
var count int32
bus.Subscribe("workflow.assigned.*", func(e Event) {
atomic.AddInt32(&count, 1)
})
bus.Publish(Event{Label: "workflow.assigned.abc", Payload: json.RawMessage(`{}`)})
bus.Publish(Event{Label: "workflow.assigned.xyz", Payload: json.RawMessage(`{}`)})
bus.Publish(Event{Label: "workflow.claimed.abc", Payload: json.RawMessage(`{}`)})
if atomic.LoadInt32(&count) != 2 {
t.Errorf("expected 2 dispatches, got %d", count)
}
}
func TestBusUnsubscribe(t *testing.T) {
bus := NewBus()
var count int32
unsub := bus.Subscribe("test.event", func(e Event) {
atomic.AddInt32(&count, 1)
})
bus.Publish(Event{Label: "test.event", Payload: json.RawMessage(`{}`)})
unsub()
bus.Publish(Event{Label: "test.event", Payload: json.RawMessage(`{}`)})
if atomic.LoadInt32(&count) != 1 {
t.Errorf("expected 1 dispatch after unsub, got %d", count)
}
}
func TestBusGlobalWildcard(t *testing.T) {
bus := NewBus()
var count int32
bus.Subscribe("*", func(e Event) {
atomic.AddInt32(&count, 1)
})
bus.Publish(Event{Label: "workflow.assigned.abc", Payload: json.RawMessage(`{}`)})
bus.Publish(Event{Label: "system.notify", Payload: json.RawMessage(`{}`)})
bus.Publish(Event{Label: "plugin.hook.pre_completion", Payload: json.RawMessage(`{}`)})
if atomic.LoadInt32(&count) != 3 {
t.Errorf("expected 3 dispatches, got %d", count)
}
}
func TestRouteFor(t *testing.T) {
tests := []struct {
label string
want Direction
}{
{"system.notify", DirToClient},
{"plugin.hook.pre_completion", DirLocal},
{"internal.db.write", DirLocal},
{"unknown.event", DirLocal}, // default
{"ping", DirFromClient},
{"pong", DirToClient},
// Tool bridge routes
{"tool.call.abc123", DirToClient},
{"tool.result.abc123", DirBoth},
// Extension lifecycle
{"extension.loaded", DirLocal},
{"extension.error", DirLocal},
// Realtime
{"realtime.chat.message", DirToClient},
{"realtime.custom.event", DirToClient},
// Room management
{"room.subscribe", DirFromClient},
{"room.unsubscribe", DirFromClient},
}
for _, tt := range tests {
got := RouteFor(tt.label)
if got != tt.want {
t.Errorf("RouteFor(%q) = %d, want %d", tt.label, got, tt.want)
}
}
}
func TestWaitFor_Success(t *testing.T) {
bus := NewBus()
// Publish after a short delay
go func() {
time.Sleep(10 * time.Millisecond)
bus.Publish(Event{
Label: "tool.result.abc123",
Payload: json.RawMessage(`{"result":"hello"}`),
})
}()
event, ok := bus.WaitFor("tool.result.abc123", 1*time.Second)
if !ok {
t.Fatal("WaitFor timed out, expected success")
}
if event.Label != "tool.result.abc123" {
t.Errorf("expected label tool.result.abc123, got %s", event.Label)
}
if string(event.Payload) != `{"result":"hello"}` {
t.Errorf("unexpected payload: %s", string(event.Payload))
}
}
func TestWaitFor_Timeout(t *testing.T) {
bus := NewBus()
_, ok := bus.WaitFor("tool.result.never", 50*time.Millisecond)
if ok {
t.Error("WaitFor should have timed out")
}
}
func TestWaitFor_IgnoresOtherLabels(t *testing.T) {
bus := NewBus()
go func() {
time.Sleep(5 * time.Millisecond)
bus.Publish(Event{Label: "tool.result.other", Payload: json.RawMessage(`{}`)})
time.Sleep(5 * time.Millisecond)
bus.Publish(Event{Label: "tool.result.target", Payload: json.RawMessage(`{"ok":true}`)})
}()
event, ok := bus.WaitFor("tool.result.target", 1*time.Second)
if !ok {
t.Fatal("WaitFor timed out")
}
if event.Label != "tool.result.target" {
t.Errorf("got wrong label: %s", event.Label)
}
}
func TestWaitFor_CleansUpSubscription(t *testing.T) {
bus := NewBus()
// WaitFor with immediate timeout
bus.WaitFor("tool.result.cleanup", 1*time.Millisecond)
time.Sleep(5 * time.Millisecond)
// Verify no panic when publishing to the label after cleanup
bus.Publish(Event{Label: "tool.result.cleanup", Payload: json.RawMessage(`{}`)})
}
func TestRealtimeRouting(t *testing.T) {
// realtime.* events should be sent to clients but not accepted from them
if !ShouldSendToClient("realtime.chat.message") {
t.Error("realtime.* should be sent to client")
}
if ShouldAcceptFromClient("realtime.chat.message") {
t.Error("realtime.* should NOT be accepted from client")
}
// room.subscribe/unsubscribe should be accepted from clients
if !ShouldAcceptFromClient("room.subscribe") {
t.Error("room.subscribe should be accepted from client")
}
if !ShouldAcceptFromClient("room.unsubscribe") {
t.Error("room.unsubscribe should be accepted from client")
}
if ShouldSendToClient("room.subscribe") {
t.Error("room.subscribe should NOT be sent to client")
}
}
func TestToolCallRouteToClient(t *testing.T) {
if !ShouldSendToClient("tool.call.abc123") {
t.Error("tool.call.* should be sent to client")
}
if ShouldAcceptFromClient("tool.call.abc123") {
t.Error("tool.call.* should NOT be accepted from client")
}
}
func TestToolResultRouteBoth(t *testing.T) {
// The WS subscriber explicitly filters out tool.result events to
// prevent re-sending to clients (see subscribeToBus in ws.go).
if !ShouldAcceptFromClient("tool.result.abc123") {
t.Error("tool.result.* should be accepted from client")
}
if !ShouldSendToClient("tool.result.abc123") {
t.Error("tool.result.* should route DirBoth (filtered by WS subscriber, not routing table)")
}
}
func TestShellContractEventRoutes(t *testing.T) {
// package.changed — broadcast to all clients
if !ShouldSendToClient("package.changed") {
t.Error("package.changed should be sent to client")
}
if ShouldAcceptFromClient("package.changed") {
t.Error("package.changed should NOT be accepted from client")
}
// auth.changed — targeted to specific user
if !ShouldSendToClient("auth.changed") {
t.Error("auth.changed should be sent to client")
}
if ShouldAcceptFromClient("auth.changed") {
t.Error("auth.changed should NOT be accepted from client")
}
// notification.all_read — targeted to specific user
if !ShouldSendToClient("notification.all_read") {
t.Error("notification.all_read should be sent to client")
}
if ShouldAcceptFromClient("notification.all_read") {
t.Error("notification.all_read should NOT be accepted from client")
}
}