package ws import ( "encoding/json" "testing" "github.com/stretchr/testify/assert" ) // === Event type constants tests === // Verify all Chatwoot-style dot-separated event naming conventions. func TestMessageEventConstants(t *testing.T) { assert.Equal(t, "message.created", EventMessageCreated) assert.Equal(t, "message.updated", EventMessageUpdated) assert.Equal(t, "message.deleted", EventMessageDeleted) } func TestConversationEventConstants(t *testing.T) { assert.Equal(t, "conversation.created", EventConversationCreated) assert.Equal(t, "conversation.updated", EventConversationUpdated) assert.Equal(t, "conversation.resolved", EventConversationResolved) assert.Equal(t, "conversation.opened", EventConversationOpened) assert.Equal(t, "conversation.assigned", EventConversationAssigned) assert.Equal(t, "conversation.unassigned", EventConversationUnassigned) assert.Equal(t, "conversation.status_changed", EventConversationStatusChanged) assert.Equal(t, "conversation.typing_on", EventConversationTypingOn) assert.Equal(t, "conversation.typing_off", EventConversationTypingOff) assert.Equal(t, "conversation.mentioned", EventConversationMentioned) assert.Equal(t, "conversation.read", EventConversationRead) } func TestContactEventConstants(t *testing.T) { assert.Equal(t, "contact.created", EventContactCreated) assert.Equal(t, "contact.updated", EventContactUpdated) assert.Equal(t, "contact.deleted", EventContactDeleted) assert.Equal(t, "contact.merged", EventContactMerged) } func TestInboxEventConstants(t *testing.T) { assert.Equal(t, "inbox.created", EventInboxCreated) assert.Equal(t, "inbox.updated", EventInboxUpdated) assert.Equal(t, "inbox.deleted", EventInboxDeleted) } func TestNotificationEventConstants(t *testing.T) { assert.Equal(t, "notification.created", EventNotificationCreated) assert.Equal(t, "notification.updated", EventNotificationUpdated) assert.Equal(t, "notification.deleted", EventNotificationDeleted) } func TestPresenceEventConstants(t *testing.T) { assert.Equal(t, "presence.update", EventPresenceUpdate) assert.Equal(t, "account.cache_invalidated", EventAccountCacheInvalidated) } func TestAgentTypingEventConstants(t *testing.T) { assert.Equal(t, "agent.typing_on", EventAgentTypingOn) assert.Equal(t, "agent.typing_off", EventAgentTypingOff) assert.Equal(t, "agent.online", EventAgentOnline) assert.Equal(t, "agent.offline", EventAgentOffline) } func TestProtocolControlConstants(t *testing.T) { assert.Equal(t, "subscribe.confirm", EventSubscribeConfirm) assert.Equal(t, "unsubscribe.confirm", EventUnsubscribeConfirm) assert.Equal(t, "subscribe.reject", EventSubscribeReject) assert.Equal(t, "ping.response", EventPingResponse) assert.Equal(t, "welcome", EventWelcome) assert.Equal(t, "disconnect", EventDisconnect) } func TestEventNamingConvention_DotSeparated(t *testing.T) { // All business events should follow Chatwoot's resource.action naming events := []string{ EventMessageCreated, EventMessageUpdated, EventMessageDeleted, EventConversationCreated, EventConversationUpdated, EventConversationResolved, EventContactCreated, EventContactUpdated, EventContactDeleted, EventContactMerged, EventInboxCreated, EventInboxUpdated, EventInboxDeleted, EventNotificationCreated, EventNotificationUpdated, EventNotificationDeleted, EventPresenceUpdate, EventAccountCacheInvalidated, EventAgentTypingOn, EventAgentTypingOff, EventAgentOnline, EventAgentOffline, } for _, event := range events { assert.Contains(t, event, ".", "event %s should follow resource.action naming", event) } } // === WSMessage struct tests === func TestWSMessage_MarshalUnmarshal(t *testing.T) { msg := WSMessage{ Event: EventMessageCreated, Data: map[string]interface{}{"id": 1, "content": "hello"}, AccountID: 100, } data, err := json.Marshal(msg) assert.NoError(t, err) assert.Contains(t, string(data), "message.created") assert.Contains(t, string(data), "account_id") var decoded WSMessage err = json.Unmarshal(data, &decoded) assert.NoError(t, err) assert.Equal(t, EventMessageCreated, decoded.Event) assert.Equal(t, uint(100), decoded.AccountID) } func TestWSMessage_WithPerformer(t *testing.T) { msg := WSMessage{ Event: EventConversationUpdated, Data: map[string]interface{}{"status": "resolved"}, Performer: &Performer{ ID: 1, Name: "Agent Smith", Type: "user", AvatarURL: "https://example.com/avatar.png", }, } data, err := json.Marshal(msg) assert.NoError(t, err) assert.Contains(t, string(data), "performer") assert.Contains(t, string(data), "Agent Smith") } // === WSCommand struct tests === func TestWSCommand_MarshalUnmarshal(t *testing.T) { cmd := WSCommand{ Command: "subscribe", Data: `{"channel":"AccountChannel","account_id":100}`, } data, err := json.Marshal(cmd) assert.NoError(t, err) var decoded WSCommand err = json.Unmarshal(data, &decoded) assert.NoError(t, err) assert.Equal(t, "subscribe", decoded.Command) } func TestWSCommand_CommandTypes(t *testing.T) { commands := []string{"subscribe", "unsubscribe", "ping", "typing_on", "typing_off", "update_presence"} for _, cmd := range commands { wsCmd := WSCommand{Command: cmd} assert.Equal(t, cmd, wsCmd.Command) } } // === WSClaims struct tests === func TestWSClaims_UserAuth(t *testing.T) { claims := WSClaims{ UserID: 1, AccountID: 100, Role: "agent", Provider: "email", IsContact: false, } assert.Equal(t, uint(1), claims.UserID) assert.Equal(t, uint(100), claims.AccountID) assert.Equal(t, "agent", claims.Role) assert.False(t, claims.IsContact) } func TestWSClaims_ContactAuth(t *testing.T) { claims := WSClaims{ AccountID: 100, Role: "contact", Provider: "pubsub_token", PubsubToken: "abc123", IsContact: true, ContactID: 50, InboxID: 10, } assert.True(t, claims.IsContact) assert.Equal(t, uint(50), claims.ContactID) assert.Equal(t, uint(10), claims.InboxID) assert.NotEmpty(t, claims.PubsubToken) } // === SubscribeData struct tests === func TestSubscribeData_AccountChannel(t *testing.T) { sd := SubscribeData{ Channel: "AccountChannel", AccountID: 100, } assert.Equal(t, "AccountChannel", sd.Channel) assert.Equal(t, uint(100), sd.AccountID) } func TestSubscribeData_ConversationChannel(t *testing.T) { sd := SubscribeData{ Channel: "ConversationChannel", AccountID: 100, ConversationID: 42, } assert.Equal(t, "ConversationChannel", sd.Channel) assert.Equal(t, uint(42), sd.ConversationID) } func TestSubscribeData_ContactAuth(t *testing.T) { sd := SubscribeData{ Channel: "ConversationChannel", AccountID: 100, PubsubToken: "token_abc", ConversationID: 42, } assert.NotEmpty(t, sd.PubsubToken) } // === TypingData struct tests === func TestTypingData(t *testing.T) { td := TypingData{ ConversationID: 42, AccountID: 100, } assert.Equal(t, uint(42), td.ConversationID) assert.Equal(t, uint(100), td.AccountID) } // === Performer struct tests === func TestPerformer_UserType(t *testing.T) { p := Performer{ ID: 1, Name: "Agent One", Type: "user", AvatarURL: "https://example.com/avatar1.png", } assert.Equal(t, "user", p.Type) } func TestPerformer_ContactType(t *testing.T) { p := Performer{ ID: 50, Name: "Contact Five", Type: "contact", AvatarURL: "", } assert.Equal(t, "contact", p.Type) }