286 lines
8.0 KiB
Go
286 lines
8.0 KiB
Go
package ws
|
|
|
|
import (
|
|
"encoding/json"
|
|
"sync"
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
// === EventPublisher Tests ===
|
|
// Uses mockHandler (from broadcast_test.go) as the MessageHandler implementation,
|
|
// plus SSERegistry for SSE delivery verification.
|
|
|
|
func TestEventPublisher_LocalDelivery(t *testing.T) {
|
|
hub := newMockHandler()
|
|
sse := NewSSERegistry()
|
|
publisher := NewEventPublisherLocal(hub, sse)
|
|
|
|
// Subscribe SSE client to account 1
|
|
ch := sse.Subscribe("sse1", 1, 100)
|
|
require.NotNil(t, ch)
|
|
|
|
payload := map[string]interface{}{"id": 1, "content": "hello"}
|
|
publisher.PublishEvent(1, EventMessageCreated, payload)
|
|
|
|
// Verify WebSocket Hub received the event
|
|
data := hub.getAccountData(1)
|
|
require.NotNil(t, data, "Hub should have received event for account 1")
|
|
|
|
var wsMsg WSMessage
|
|
err := json.Unmarshal(data, &wsMsg)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, EventMessageCreated, wsMsg.Event)
|
|
assert.Equal(t, uint(1), wsMsg.AccountID)
|
|
|
|
// Verify SSE channel received the event
|
|
select {
|
|
case event := <-ch.Events:
|
|
assert.Equal(t, EventMessageCreated, event.Type)
|
|
default:
|
|
t.Fatal("SSE channel should have received event")
|
|
}
|
|
}
|
|
|
|
func TestEventPublisher_AccountRouting(t *testing.T) {
|
|
hub := newMockHandler()
|
|
sse := NewSSERegistry()
|
|
publisher := NewEventPublisherLocal(hub, sse)
|
|
|
|
// Subscribe SSE clients to different accounts
|
|
ch1 := sse.Subscribe("sse1", 1, 100)
|
|
ch2 := sse.Subscribe("sse2", 2, 200)
|
|
require.NotNil(t, ch1)
|
|
require.NotNil(t, ch2)
|
|
|
|
payload := map[string]interface{}{"id": 1}
|
|
publisher.PublishEvent(1, EventConversationCreated, payload)
|
|
|
|
// Account 1 SSE client should receive
|
|
select {
|
|
case event := <-ch1.Events:
|
|
assert.Equal(t, EventConversationCreated, event.Type)
|
|
default:
|
|
t.Fatal("account 1 SSE client should receive event")
|
|
}
|
|
|
|
// Account 2 SSE client should NOT receive
|
|
assertNoSSEEvent(t, ch2)
|
|
}
|
|
|
|
func TestEventPublisher_ConversationEvent(t *testing.T) {
|
|
hub := newMockHandler()
|
|
sse := NewSSERegistry()
|
|
publisher := NewEventPublisherLocal(hub, sse)
|
|
|
|
// Subscribe SSE clients
|
|
chBroad := sse.Subscribe("sse_broad", 1, 100) // no conv filter
|
|
chFiltered := sse.Subscribe("sse_filtered", 1, 200)
|
|
sse.SubscribeConversation("sse_filtered", 42)
|
|
|
|
require.NotNil(t, chBroad)
|
|
require.NotNil(t, chFiltered)
|
|
|
|
payload := map[string]interface{}{"message": "typing..."}
|
|
publisher.PublishConversationEvent(1, 42, EventConversationTypingOn, payload)
|
|
|
|
// Broad SSE client should receive (no conversation filter)
|
|
assertSSEEventReceived(t, chBroad, SSEEvent{Type: EventConversationTypingOn})
|
|
|
|
// Filtered SSE client should receive (subscribed to conv 42)
|
|
assertSSEEventReceived(t, chFiltered, SSEEvent{Type: EventConversationTypingOn})
|
|
}
|
|
|
|
func TestEventPublisher_ConversationEvent_FilteredOut(t *testing.T) {
|
|
hub := newMockHandler()
|
|
sse := NewSSERegistry()
|
|
publisher := NewEventPublisherLocal(hub, sse)
|
|
|
|
// Subscribe SSE client to conversation 99 only
|
|
ch := sse.Subscribe("sse1", 1, 100)
|
|
sse.SubscribeConversation("sse1", 99)
|
|
require.NotNil(t, ch)
|
|
|
|
payload := map[string]interface{}{"message": "typing..."}
|
|
publisher.PublishConversationEvent(1, 42, EventConversationTypingOn, payload)
|
|
|
|
// Client subscribed to conv 99 should NOT receive conv 42 event
|
|
assertNoSSEEvent(t, ch)
|
|
}
|
|
|
|
func TestEventPublisher_AllEventTypes(t *testing.T) {
|
|
hub := newMockHandler()
|
|
sse := NewSSERegistry()
|
|
publisher := NewEventPublisherLocal(hub, sse)
|
|
|
|
ch := sse.Subscribe("sse1", 1, 100)
|
|
require.NotNil(t, ch)
|
|
|
|
eventTypes := []string{
|
|
EventMessageCreated,
|
|
EventMessageUpdated,
|
|
EventMessageDeleted,
|
|
EventConversationCreated,
|
|
EventConversationUpdated,
|
|
EventConversationStatusChanged,
|
|
EventAssigneeChanged,
|
|
EventTeamChanged,
|
|
EventContactCreated,
|
|
EventContactUpdated,
|
|
EventContactDeleted,
|
|
EventInboxCreated,
|
|
EventInboxUpdated,
|
|
EventInboxDeleted,
|
|
EventNotificationCreated,
|
|
EventNotificationUpdated,
|
|
}
|
|
|
|
for _, eventType := range eventTypes {
|
|
payload := map[string]interface{}{"test": eventType}
|
|
publisher.PublishEvent(1, eventType, payload)
|
|
|
|
// Verify SSE delivery
|
|
select {
|
|
case event := <-ch.Events:
|
|
assert.Equal(t, eventType, event.Type)
|
|
default:
|
|
t.Fatalf("SSE channel should receive event type: %s", eventType)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestEventPublisher_NoHub(t *testing.T) {
|
|
sse := NewSSERegistry()
|
|
// Create publisher with no hub — SSE should still work
|
|
publisher := NewEventPublisherLocal(nil, sse)
|
|
|
|
ch := sse.Subscribe("sse1", 1, 100)
|
|
require.NotNil(t, ch)
|
|
|
|
payload := map[string]interface{}{"id": 1}
|
|
publisher.PublishEvent(1, EventMessageCreated, payload)
|
|
|
|
// SSE should still receive the event
|
|
select {
|
|
case event := <-ch.Events:
|
|
assert.Equal(t, EventMessageCreated, event.Type)
|
|
default:
|
|
t.Fatal("SSE channel should receive event even without hub")
|
|
}
|
|
}
|
|
|
|
func TestEventPublisher_NoSSE(t *testing.T) {
|
|
hub := newMockHandler()
|
|
// Create publisher with no SSE — hub should still work
|
|
publisher := NewEventPublisherLocal(hub, nil)
|
|
|
|
payload := map[string]interface{}{"id": 1}
|
|
publisher.PublishEvent(1, EventMessageCreated, payload)
|
|
|
|
// Hub should still receive the event
|
|
data := hub.getAccountData(1)
|
|
require.NotNil(t, data, "Hub should receive event even without SSE")
|
|
}
|
|
|
|
func TestEventPublisher_NilTargets(t *testing.T) {
|
|
// Both hub and SSE are nil — should not panic
|
|
publisher := NewEventPublisherLocal(nil, nil)
|
|
|
|
payload := map[string]interface{}{"id": 1}
|
|
publisher.PublishEvent(1, EventMessageCreated, payload)
|
|
// No crash = success
|
|
}
|
|
|
|
func TestEventPublisher_WSMessageFormat(t *testing.T) {
|
|
hub := newMockHandler()
|
|
sse := NewSSERegistry()
|
|
publisher := NewEventPublisherLocal(hub, sse)
|
|
|
|
payload := map[string]interface{}{
|
|
"id": 42,
|
|
"content": "test message",
|
|
"status": "open",
|
|
}
|
|
publisher.PublishEvent(1, EventConversationCreated, payload)
|
|
|
|
data := hub.getAccountData(1)
|
|
require.NotNil(t, data)
|
|
|
|
var wsMsg WSMessage
|
|
err := json.Unmarshal(data, &wsMsg)
|
|
require.NoError(t, err)
|
|
|
|
assert.Equal(t, EventConversationCreated, wsMsg.Event)
|
|
assert.Equal(t, uint(1), wsMsg.AccountID)
|
|
// Data should contain the payload fields
|
|
dataMap, ok := wsMsg.Data.(map[string]interface{})
|
|
require.True(t, ok, "Data should be a map")
|
|
assert.Equal(t, float64(42), dataMap["id"])
|
|
assert.Equal(t, "test message", dataMap["content"])
|
|
}
|
|
|
|
// === Mock MessageHandler reuse ===
|
|
// The mockHandler type is defined in broadcast_test.go in the same package.
|
|
// We reuse it here for EventPublisher tests. If needed, here's a duplicate
|
|
// for reference (Go test files in the same package share types):
|
|
//
|
|
// Note: mockHandler is already defined in broadcast_test.go and accessible
|
|
// from this test file since they're in the same Go package (ws).
|
|
|
|
// Additional mock for tracking room-based sends
|
|
type mockRoomHandler struct {
|
|
mu sync.Mutex
|
|
accounts map[uint][]byte
|
|
rooms map[string][]byte
|
|
}
|
|
|
|
func newMockRoomHandler() *mockRoomHandler {
|
|
return &mockRoomHandler{
|
|
accounts: make(map[uint][]byte),
|
|
rooms: make(map[string][]byte),
|
|
}
|
|
}
|
|
|
|
func (m *mockRoomHandler) SendToAccount(accountID uint, data []byte) {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
m.accounts[accountID] = data
|
|
}
|
|
|
|
func (m *mockRoomHandler) SendToRoom(room string, data []byte) {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
m.rooms[room] = data
|
|
}
|
|
|
|
func (m *mockRoomHandler) getAccountData(accountID uint) []byte {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
return m.accounts[accountID]
|
|
}
|
|
|
|
func (m *mockRoomHandler) getRoomData(room string) []byte {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
return m.rooms[room]
|
|
}
|
|
|
|
func TestEventPublisher_ConversationEvent_RoomDelivery(t *testing.T) {
|
|
hub := newMockRoomHandler()
|
|
sse := NewSSERegistry()
|
|
publisher := NewEventPublisherLocal(hub, sse)
|
|
|
|
payload := map[string]interface{}{"message": "typing..."}
|
|
publisher.PublishConversationEvent(1, 42, EventConversationTypingOn, payload)
|
|
|
|
// Verify account room received
|
|
data := hub.getAccountData(1)
|
|
require.NotNil(t, data, "Hub account room should receive event")
|
|
|
|
// Verify conversation room received
|
|
convRoom := conversationRoomNameHelper(1, 42)
|
|
roomData := hub.getRoomData(convRoom)
|
|
require.NotNil(t, roomData, "Hub conversation room should receive event")
|
|
} |