package events_test import ( "context" "sync" "testing" pushv1 "galaxy/backend/proto/push/v1" "galaxy/gateway/internal/events" "galaxy/gateway/internal/push" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) type capturePublisher struct { mu sync.Mutex events []push.Event } func (c *capturePublisher) Publish(event push.Event) { c.mu.Lock() defer c.mu.Unlock() c.events = append(c.events, event) } func (c *capturePublisher) snapshot() []push.Event { c.mu.Lock() defer c.mu.Unlock() out := make([]push.Event, len(c.events)) copy(out, c.events) return out } type captureInvalidator struct { mu sync.Mutex devices []string users []string } func (c *captureInvalidator) RevokeDeviceSession(id string) { c.mu.Lock() defer c.mu.Unlock() c.devices = append(c.devices, id) } func (c *captureInvalidator) RevokeAllForUser(id string) { c.mu.Lock() defer c.mu.Unlock() c.users = append(c.users, id) } func (c *captureInvalidator) snapshot() ([]string, []string) { c.mu.Lock() defer c.mu.Unlock() d := append([]string(nil), c.devices...) u := append([]string(nil), c.users...) return d, u } func TestDispatcherForwardsClientEventToPublisher(t *testing.T) { t.Parallel() pub := &capturePublisher{} inv := &captureInvalidator{} disp := events.NewDispatcher(pub, inv, nil, nil) disp.Handle(context.Background(), &pushv1.PushEvent{ Cursor: "00000000000000000001", Kind: &pushv1.PushEvent_ClientEvent{ ClientEvent: &pushv1.ClientEvent{ UserId: "user-1", DeviceSessionId: "device-1", Kind: "lobby.invite.received", Payload: []byte(`{"x":1}`), EventId: "route-1", RequestId: "req-1", TraceId: "trace-1", }, }, }) got := pub.snapshot() require.Len(t, got, 1) assert.Equal(t, push.Event{ UserID: "user-1", DeviceSessionID: "device-1", EventType: "lobby.invite.received", EventID: "route-1", PayloadBytes: []byte(`{"x":1}`), RequestID: "req-1", TraceID: "trace-1", }, got[0]) devices, users := inv.snapshot() assert.Empty(t, devices) assert.Empty(t, users) } func TestDispatcherDropsClientEventMissingEventID(t *testing.T) { t.Parallel() pub := &capturePublisher{} disp := events.NewDispatcher(pub, &captureInvalidator{}, nil, nil) disp.Handle(context.Background(), &pushv1.PushEvent{ Kind: &pushv1.PushEvent_ClientEvent{ ClientEvent: &pushv1.ClientEvent{ UserId: "user-1", Kind: "lobby.invite.received", }, }, }) assert.Empty(t, pub.snapshot()) } func TestDispatcherSessionInvalidationByDeviceID(t *testing.T) { t.Parallel() inv := &captureInvalidator{} disp := events.NewDispatcher(&capturePublisher{}, inv, nil, nil) disp.Handle(context.Background(), &pushv1.PushEvent{ Kind: &pushv1.PushEvent_SessionInvalidation{ SessionInvalidation: &pushv1.SessionInvalidation{ UserId: "user-1", DeviceSessionId: "device-1", Reason: "auth.revoke_session", }, }, }) devices, users := inv.snapshot() assert.Equal(t, []string{"device-1"}, devices) assert.Empty(t, users) } func TestDispatcherSessionInvalidationFanOutForUser(t *testing.T) { t.Parallel() inv := &captureInvalidator{} disp := events.NewDispatcher(&capturePublisher{}, inv, nil, nil) disp.Handle(context.Background(), &pushv1.PushEvent{ Kind: &pushv1.PushEvent_SessionInvalidation{ SessionInvalidation: &pushv1.SessionInvalidation{ UserId: "user-1", Reason: "auth.revoke_all_for_user", }, }, }) devices, users := inv.snapshot() assert.Empty(t, devices) assert.Equal(t, []string{"user-1"}, users) }