Align GoChat with Chatwoot frontend contracts
This commit is contained in:
@@ -43,6 +43,20 @@ func (n *noopTypingIndicatorWidget) SetTypingOff(_ context.Context, _ uint, _ ui
|
||||
return nil
|
||||
}
|
||||
|
||||
type recordingWidgetEventPublisher struct {
|
||||
accountID uint
|
||||
pubsubToken string
|
||||
eventType string
|
||||
payload interface{}
|
||||
}
|
||||
|
||||
func (p *recordingWidgetEventPublisher) PublishWidgetEvent(accountID uint, pubsubToken string, eventType string, payload interface{}) {
|
||||
p.accountID = accountID
|
||||
p.pubsubToken = pubsubToken
|
||||
p.eventType = eventType
|
||||
p.payload = payload
|
||||
}
|
||||
|
||||
type recordingWidgetTranscriptDeliverer struct {
|
||||
requests []automation.AutomationTranscriptRequest
|
||||
err error
|
||||
@@ -332,13 +346,61 @@ func TestWidgetHandler_ChatwootConfig_Success(t *testing.T) {
|
||||
require.NoError(t, json.Unmarshal(w.Body.Bytes(), &resp))
|
||||
|
||||
config := resp["website_channel_config"].(map[string]interface{})
|
||||
assertWidgetConfigFixtureShape(t, config)
|
||||
assert.NotEmpty(t, config["auth_token"])
|
||||
assert.Equal(t, "handler_ws_token_123", config["website_token"])
|
||||
assert.Equal(t, "Handler Widget Inbox", config["website_name"])
|
||||
|
||||
contact := resp["contact"].(map[string]interface{})
|
||||
assertWidgetConfigContactFixtureShape(t, contact)
|
||||
assert.NotEmpty(t, contact["id"])
|
||||
assert.NotEmpty(t, contact["pubsub_token"])
|
||||
|
||||
globalConfig := resp["global_config"].(map[string]interface{})
|
||||
assert.Contains(t, globalConfig, "directUploadsEnabled")
|
||||
assert.Contains(t, globalConfig, "maximumFileUploadSize")
|
||||
}
|
||||
|
||||
func TestWidgetHandler_ChatwootConfigPreChatFormOptionsMatchFrontendMixin(t *testing.T) {
|
||||
db, router, _ := setupWidgetHandlerTest(t)
|
||||
_, inbox := seedWidgetHandlerData(t, db)
|
||||
channelConfig := map[string]interface{}{
|
||||
"website_token": "handler_ws_token_123",
|
||||
"hmac_token": "handler_hmac_secret",
|
||||
"widget_color": "#1f93ff",
|
||||
"welcome_title": "Hi",
|
||||
"welcome_tagline": "We reply fast",
|
||||
"pre_chat_fields_enabled": true,
|
||||
"pre_chat_message": "Tell us about yourself",
|
||||
"selected_feature_flags": []string{"attachments", "emoji_picker", "end_conversation"},
|
||||
"continuity_via_email": true,
|
||||
"offline_message_enabled": true,
|
||||
"offline_message_title": "We are away",
|
||||
"offline_message_description": "Leave a message",
|
||||
}
|
||||
configJSON, err := json.Marshal(channelConfig)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, db.Model(inbox).Update("channel_config", string(configJSON)).Error)
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
req, _ := http.NewRequest("POST", "/api/v1/widget/config?website_token=handler_ws_token_123", nil)
|
||||
router.ServeHTTP(w, req)
|
||||
require.Equal(t, http.StatusOK, w.Code)
|
||||
|
||||
var resp map[string]interface{}
|
||||
require.NoError(t, json.Unmarshal(w.Body.Bytes(), &resp))
|
||||
config := resp["website_channel_config"].(map[string]interface{})
|
||||
assert.True(t, config["preChatFormEnabled"].(bool))
|
||||
options := config["preChatFormOptions"].(map[string]interface{})
|
||||
assert.Equal(t, "Tell us about yourself", options["pre_chat_message"])
|
||||
fields := options["pre_chat_fields"].([]interface{})
|
||||
require.NotEmpty(t, fields)
|
||||
firstField := fields[0].(map[string]interface{})
|
||||
for _, key := range []string{"label", "name", "type", "field_type", "required", "enabled"} {
|
||||
assert.Contains(t, firstField, key)
|
||||
}
|
||||
assert.Equal(t, true, firstField["enabled"])
|
||||
assert.Contains(t, config["enabledFeatures"], "attachments")
|
||||
}
|
||||
|
||||
func TestWidgetHandler_ChatwootConfig_InvalidWebsiteTokenReturnsNotFound(t *testing.T) {
|
||||
@@ -389,6 +451,144 @@ func TestWidgetHandler_ChatwootConfig_InvalidAuthTokenCreatesNewContact(t *testi
|
||||
assert.Contains(t, resp, "global_config")
|
||||
}
|
||||
|
||||
func TestWidgetHandler_ChatwootConversationQueryTokenReusesSession(t *testing.T) {
|
||||
db, router, _ := setupWidgetHandlerTest(t)
|
||||
_, inbox := seedWidgetHandlerData(t, db)
|
||||
|
||||
wConfig := httptest.NewRecorder()
|
||||
reqConfig, _ := http.NewRequest("POST", "/api/v1/widget/config?website_token=handler_ws_token_123", nil)
|
||||
router.ServeHTTP(wConfig, reqConfig)
|
||||
require.Equal(t, http.StatusOK, wConfig.Code)
|
||||
var configResp map[string]interface{}
|
||||
require.NoError(t, json.Unmarshal(wConfig.Body.Bytes(), &configResp))
|
||||
authToken := configResp["contact"].(map[string]interface{})["pubsub_token"].(string)
|
||||
|
||||
messageBody, _ := json.Marshal(map[string]interface{}{"message": map[string]interface{}{"content": "Popout session message"}})
|
||||
wMessage := httptest.NewRecorder()
|
||||
reqMessage, _ := http.NewRequest("POST", "/api/v1/widget/messages?cw_conversation="+authToken, bytes.NewReader(messageBody))
|
||||
reqMessage.Header.Set("Content-Type", "application/json")
|
||||
router.ServeHTTP(wMessage, reqMessage)
|
||||
require.Equal(t, http.StatusOK, wMessage.Code, wMessage.Body.String())
|
||||
|
||||
var messageResp map[string]interface{}
|
||||
require.NoError(t, json.Unmarshal(wMessage.Body.Bytes(), &messageResp))
|
||||
assert.Equal(t, "Popout session message", messageResp["content"])
|
||||
assert.Equal(t, "incoming", messageResp["message_type"])
|
||||
assertWidgetMessageFixtureShape(t, messageResp)
|
||||
|
||||
wLatest := httptest.NewRecorder()
|
||||
reqLatest, _ := http.NewRequest("GET", "/api/v1/widget/messages?cw_conversation="+authToken, nil)
|
||||
router.ServeHTTP(wLatest, reqLatest)
|
||||
require.Equal(t, http.StatusOK, wLatest.Code, wLatest.Body.String())
|
||||
var latestResp map[string]interface{}
|
||||
require.NoError(t, json.Unmarshal(wLatest.Body.Bytes(), &latestResp))
|
||||
payload := latestResp["payload"].([]interface{})
|
||||
require.Len(t, payload, 1)
|
||||
assert.Equal(t, "Popout session message", payload[0].(map[string]interface{})["content"])
|
||||
|
||||
var conversation model.Conversation
|
||||
require.NoError(t, db.Where("inbox_id = ?", inbox.ID).First(&conversation).Error)
|
||||
assert.Equal(t, string(model.ConversationStatusOpen), conversation.Status)
|
||||
}
|
||||
|
||||
func TestWidgetHandler_ChatwootConfigCwConversationReusesContactInbox(t *testing.T) {
|
||||
db, router, _ := setupWidgetHandlerTest(t)
|
||||
_, inbox := seedWidgetHandlerData(t, db)
|
||||
|
||||
wConfig := httptest.NewRecorder()
|
||||
reqConfig, _ := http.NewRequest("POST", "/api/v1/widget/config?website_token=handler_ws_token_123", nil)
|
||||
router.ServeHTTP(wConfig, reqConfig)
|
||||
require.Equal(t, http.StatusOK, wConfig.Code)
|
||||
var configResp map[string]interface{}
|
||||
require.NoError(t, json.Unmarshal(wConfig.Body.Bytes(), &configResp))
|
||||
authToken := configResp["contact"].(map[string]interface{})["pubsub_token"].(string)
|
||||
contactID := uint(configResp["contact"].(map[string]interface{})["id"].(float64))
|
||||
|
||||
messageBody, _ := json.Marshal(map[string]interface{}{"message": map[string]interface{}{"content": "Popout boot history"}})
|
||||
wMessage := httptest.NewRecorder()
|
||||
reqMessage, _ := http.NewRequest("POST", "/api/v1/widget/messages?cw_conversation="+authToken, bytes.NewReader(messageBody))
|
||||
reqMessage.Header.Set("Content-Type", "application/json")
|
||||
router.ServeHTTP(wMessage, reqMessage)
|
||||
require.Equal(t, http.StatusOK, wMessage.Code, wMessage.Body.String())
|
||||
|
||||
var beforeContacts int64
|
||||
require.NoError(t, db.Model(&model.Contact{}).Where("account_id = ?", inbox.AccountID).Count(&beforeContacts).Error)
|
||||
wPopoutConfig := httptest.NewRecorder()
|
||||
reqPopoutConfig, _ := http.NewRequest("POST", "/api/v1/widget/config?website_token=handler_ws_token_123&cw_conversation="+authToken, nil)
|
||||
router.ServeHTTP(wPopoutConfig, reqPopoutConfig)
|
||||
require.Equal(t, http.StatusOK, wPopoutConfig.Code, wPopoutConfig.Body.String())
|
||||
var popoutConfigResp map[string]interface{}
|
||||
require.NoError(t, json.Unmarshal(wPopoutConfig.Body.Bytes(), &popoutConfigResp))
|
||||
popoutContact := popoutConfigResp["contact"].(map[string]interface{})
|
||||
assert.Equal(t, float64(contactID), popoutContact["id"])
|
||||
assert.Equal(t, authToken, popoutContact["pubsub_token"])
|
||||
assertWidgetConfigContactFixtureShape(t, popoutContact)
|
||||
|
||||
var afterContacts int64
|
||||
require.NoError(t, db.Model(&model.Contact{}).Where("account_id = ?", inbox.AccountID).Count(&afterContacts).Error)
|
||||
assert.Equal(t, beforeContacts, afterContacts)
|
||||
|
||||
wLatest := httptest.NewRecorder()
|
||||
reqLatest, _ := http.NewRequest("GET", "/api/v1/widget/messages?cw_conversation="+authToken, nil)
|
||||
router.ServeHTTP(wLatest, reqLatest)
|
||||
require.Equal(t, http.StatusOK, wLatest.Code, wLatest.Body.String())
|
||||
var latestResp map[string]interface{}
|
||||
require.NoError(t, json.Unmarshal(wLatest.Body.Bytes(), &latestResp))
|
||||
payload := latestResp["payload"].([]interface{})
|
||||
require.Len(t, payload, 1)
|
||||
assert.Equal(t, "Popout boot history", payload[0].(map[string]interface{})["content"])
|
||||
}
|
||||
|
||||
func TestWidgetHandler_ChatwootConversationCookieReusesSession(t *testing.T) {
|
||||
db, router, _ := setupWidgetHandlerTest(t)
|
||||
_, inbox := seedWidgetHandlerData(t, db)
|
||||
|
||||
wConfig := httptest.NewRecorder()
|
||||
reqConfig, _ := http.NewRequest("POST", "/api/v1/widget/config?website_token=handler_ws_token_123", nil)
|
||||
router.ServeHTTP(wConfig, reqConfig)
|
||||
require.Equal(t, http.StatusOK, wConfig.Code)
|
||||
var configResp map[string]interface{}
|
||||
require.NoError(t, json.Unmarshal(wConfig.Body.Bytes(), &configResp))
|
||||
authToken := configResp["contact"].(map[string]interface{})["pubsub_token"].(string)
|
||||
contactID := uint(configResp["contact"].(map[string]interface{})["id"].(float64))
|
||||
|
||||
messageBody, _ := json.Marshal(map[string]interface{}{"message": map[string]interface{}{"content": "Cookie persisted history"}})
|
||||
wMessage := httptest.NewRecorder()
|
||||
reqMessage, _ := http.NewRequest("POST", "/api/v1/widget/messages", bytes.NewReader(messageBody))
|
||||
reqMessage.Header.Set("Content-Type", "application/json")
|
||||
reqMessage.AddCookie(&http.Cookie{Name: "cw_conversation", Value: authToken})
|
||||
router.ServeHTTP(wMessage, reqMessage)
|
||||
require.Equal(t, http.StatusOK, wMessage.Code, wMessage.Body.String())
|
||||
|
||||
var beforeContacts int64
|
||||
require.NoError(t, db.Model(&model.Contact{}).Where("account_id = ?", inbox.AccountID).Count(&beforeContacts).Error)
|
||||
wCookieConfig := httptest.NewRecorder()
|
||||
reqCookieConfig, _ := http.NewRequest("POST", "/api/v1/widget/config?website_token=handler_ws_token_123", nil)
|
||||
reqCookieConfig.AddCookie(&http.Cookie{Name: "cw_conversation", Value: authToken})
|
||||
router.ServeHTTP(wCookieConfig, reqCookieConfig)
|
||||
require.Equal(t, http.StatusOK, wCookieConfig.Code, wCookieConfig.Body.String())
|
||||
var cookieConfigResp map[string]interface{}
|
||||
require.NoError(t, json.Unmarshal(wCookieConfig.Body.Bytes(), &cookieConfigResp))
|
||||
cookieContact := cookieConfigResp["contact"].(map[string]interface{})
|
||||
assert.Equal(t, float64(contactID), cookieContact["id"])
|
||||
assert.Equal(t, authToken, cookieContact["pubsub_token"])
|
||||
|
||||
var afterContacts int64
|
||||
require.NoError(t, db.Model(&model.Contact{}).Where("account_id = ?", inbox.AccountID).Count(&afterContacts).Error)
|
||||
assert.Equal(t, beforeContacts, afterContacts)
|
||||
|
||||
wLatest := httptest.NewRecorder()
|
||||
reqLatest, _ := http.NewRequest("GET", "/api/v1/widget/messages", nil)
|
||||
reqLatest.AddCookie(&http.Cookie{Name: "cw_conversation", Value: authToken})
|
||||
router.ServeHTTP(wLatest, reqLatest)
|
||||
require.Equal(t, http.StatusOK, wLatest.Code, wLatest.Body.String())
|
||||
var latestResp map[string]interface{}
|
||||
require.NoError(t, json.Unmarshal(wLatest.Body.Bytes(), &latestResp))
|
||||
payload := latestResp["payload"].([]interface{})
|
||||
require.Len(t, payload, 1)
|
||||
assert.Equal(t, "Cookie persisted history", payload[0].(map[string]interface{})["content"])
|
||||
}
|
||||
|
||||
func TestWidgetHandler_ChatwootMessageAppliesAttrsAndLabelsToNewConversation(t *testing.T) {
|
||||
db, router, _ := setupWidgetHandlerTest(t)
|
||||
account, _ := seedWidgetHandlerData(t, db)
|
||||
@@ -527,6 +727,7 @@ func TestWidgetHandler_ChatwootMessages_AuthTokenAndNestedPayload(t *testing.T)
|
||||
|
||||
var messageResp map[string]interface{}
|
||||
require.NoError(t, json.Unmarshal(wMessage.Body.Bytes(), &messageResp))
|
||||
assertWidgetMessageFixtureShape(t, messageResp)
|
||||
assert.Nil(t, messageResp["message"])
|
||||
assert.Equal(t, "Hello from Chatwoot widget", messageResp["content"])
|
||||
assert.NotEmpty(t, messageResp["conversation_id"])
|
||||
@@ -542,6 +743,7 @@ func TestWidgetHandler_ChatwootMessages_AuthTokenAndNestedPayload(t *testing.T)
|
||||
payload := indexResp["payload"].([]interface{})
|
||||
require.Len(t, payload, 1)
|
||||
firstMessage := payload[0].(map[string]interface{})
|
||||
assertWidgetMessageFixtureShape(t, firstMessage)
|
||||
assert.Equal(t, "Hello from Chatwoot widget", firstMessage["content"])
|
||||
|
||||
wContact := httptest.NewRecorder()
|
||||
@@ -549,6 +751,45 @@ func TestWidgetHandler_ChatwootMessages_AuthTokenAndNestedPayload(t *testing.T)
|
||||
reqContact.Header.Set("X-Auth-Token", authToken)
|
||||
router.ServeHTTP(wContact, reqContact)
|
||||
require.Equal(t, http.StatusOK, wContact.Code)
|
||||
var contactResp map[string]interface{}
|
||||
require.NoError(t, json.Unmarshal(wContact.Body.Bytes(), &contactResp))
|
||||
assertWidgetContactFixtureShape(t, contactResp)
|
||||
}
|
||||
|
||||
func TestWidgetHandler_ChatwootMessagePublishesWidgetRealtimePayload(t *testing.T) {
|
||||
db, router, handler := setupWidgetHandlerTest(t)
|
||||
_, _ = seedWidgetHandlerData(t, db)
|
||||
publisher := &recordingWidgetEventPublisher{}
|
||||
handler.WithEventPublisher(publisher)
|
||||
|
||||
wConfig := httptest.NewRecorder()
|
||||
reqConfig, _ := http.NewRequest("POST", "/api/v1/widget/config?website_token=handler_ws_token_123", nil)
|
||||
router.ServeHTTP(wConfig, reqConfig)
|
||||
require.Equal(t, http.StatusOK, wConfig.Code)
|
||||
|
||||
var configResp map[string]interface{}
|
||||
require.NoError(t, json.Unmarshal(wConfig.Body.Bytes(), &configResp))
|
||||
authToken := configResp["contact"].(map[string]interface{})["pubsub_token"].(string)
|
||||
|
||||
body, _ := json.Marshal(map[string]interface{}{
|
||||
"message": map[string]interface{}{"content": "Realtime widget message"},
|
||||
})
|
||||
wMessage := httptest.NewRecorder()
|
||||
reqMessage, _ := http.NewRequest("POST", "/api/v1/widget/messages", bytes.NewReader(body))
|
||||
reqMessage.Header.Set("Content-Type", "application/json")
|
||||
reqMessage.Header.Set("X-Auth-Token", authToken)
|
||||
router.ServeHTTP(wMessage, reqMessage)
|
||||
require.Equal(t, http.StatusOK, wMessage.Code)
|
||||
|
||||
assert.Equal(t, uint(1), publisher.accountID)
|
||||
assert.Equal(t, authToken, publisher.pubsubToken)
|
||||
assert.Equal(t, ws.EventMessageCreated, publisher.eventType)
|
||||
payload, ok := publisher.payload.(gin.H)
|
||||
require.True(t, ok, "expected widget realtime payload to use Chatwoot widget message shape")
|
||||
assert.Equal(t, "Realtime widget message", payload["content"])
|
||||
assert.Equal(t, uint(1), payload["account_id"])
|
||||
assert.NotEmpty(t, payload["conversation_id"])
|
||||
assertWidgetMessageFixtureShape(t, payload)
|
||||
}
|
||||
|
||||
func TestWidgetHandler_ChatwootMessagesIndexFiltersInternalMessages(t *testing.T) {
|
||||
@@ -717,10 +958,12 @@ func TestWidgetHandler_ChatwootMessageDirectUploadAttachment(t *testing.T) {
|
||||
|
||||
var messageResp map[string]interface{}
|
||||
require.NoError(t, json.Unmarshal(wMessage.Body.Bytes(), &messageResp))
|
||||
assertWidgetMessageFixtureShape(t, messageResp)
|
||||
assert.Empty(t, messageResp["content"])
|
||||
attachments := messageResp["attachments"].([]interface{})
|
||||
require.Len(t, attachments, 1)
|
||||
attachmentPayload := attachments[0].(map[string]interface{})
|
||||
assertWidgetAttachmentFixtureShape(t, attachmentPayload)
|
||||
assert.Equal(t, "/uploads/widget_direct/signed-widget-upload-1.png", attachmentPayload["data_url"])
|
||||
assert.Equal(t, "image", attachmentPayload["file_type"])
|
||||
assert.Equal(t, float64(account.ID), attachmentPayload["account_id"])
|
||||
@@ -743,9 +986,11 @@ func TestWidgetHandler_ChatwootMessageDirectUploadAttachment(t *testing.T) {
|
||||
payload := indexResp["payload"].([]interface{})
|
||||
require.Len(t, payload, 1)
|
||||
indexedMessage := payload[0].(map[string]interface{})
|
||||
assertWidgetMessageFixtureShape(t, indexedMessage)
|
||||
indexedAttachments := indexedMessage["attachments"].([]interface{})
|
||||
require.Len(t, indexedAttachments, 1)
|
||||
indexedAttachment := indexedAttachments[0].(map[string]interface{})
|
||||
assertWidgetAttachmentFixtureShape(t, indexedAttachment)
|
||||
assert.Equal(t, "/uploads/widget_direct/signed-widget-upload-1.png", indexedAttachment["data_url"])
|
||||
assert.Equal(t, float64(account.ID), indexedAttachment["account_id"])
|
||||
assert.NotContains(t, indexedAttachment, "created_at")
|
||||
@@ -1463,6 +1708,7 @@ func TestWidgetHandler_ChatwootCampaigns_Success(t *testing.T) {
|
||||
var resp []map[string]interface{}
|
||||
require.NoError(t, json.Unmarshal(w.Body.Bytes(), &resp))
|
||||
require.Len(t, resp, 1)
|
||||
assertWidgetCampaignFixtureShape(t, resp[0])
|
||||
assert.Equal(t, float64(42), resp[0]["id"])
|
||||
assert.Equal(t, "Welcome to support", resp[0]["message"])
|
||||
rules := resp[0]["trigger_rules"].(map[string]interface{})
|
||||
@@ -1530,6 +1776,14 @@ func TestWidgetHandler_ChatwootEventsAndLabels(t *testing.T) {
|
||||
router.ServeHTTP(wEvent, reqEvent)
|
||||
require.Equal(t, http.StatusNoContent, wEvent.Code)
|
||||
|
||||
wCookieEvent := httptest.NewRecorder()
|
||||
cookieEventBody, _ := json.Marshal(map[string]interface{}{"name": "webwidget.triggered", "event_info": map[string]interface{}{"source": "cookie"}})
|
||||
reqCookieEvent, _ := http.NewRequest("POST", "/api/v1/widget/events?website_token=handler_ws_token_123", bytes.NewReader(cookieEventBody))
|
||||
reqCookieEvent.Header.Set("Content-Type", "application/json")
|
||||
reqCookieEvent.AddCookie(&http.Cookie{Name: "cw_conversation", Value: authToken})
|
||||
router.ServeHTTP(wCookieEvent, reqCookieEvent)
|
||||
require.Equal(t, http.StatusNoContent, wCookieEvent.Code)
|
||||
|
||||
wAdd := httptest.NewRecorder()
|
||||
labelBody, _ := json.Marshal(map[string]interface{}{"label": "vip"})
|
||||
reqAdd, _ := http.NewRequest("POST", "/api/v1/widget/labels", bytes.NewReader(labelBody))
|
||||
@@ -1549,6 +1803,25 @@ func TestWidgetHandler_ChatwootEventsAndLabels(t *testing.T) {
|
||||
|
||||
require.NoError(t, db.First(&conversation, conversation.ID).Error)
|
||||
assert.Empty(t, conversation.Labels)
|
||||
|
||||
wCookieAdd := httptest.NewRecorder()
|
||||
reqCookieAdd, _ := http.NewRequest("POST", "/api/v1/widget/labels", bytes.NewReader(labelBody))
|
||||
reqCookieAdd.Header.Set("Content-Type", "application/json")
|
||||
reqCookieAdd.AddCookie(&http.Cookie{Name: "cw_conversation", Value: authToken})
|
||||
router.ServeHTTP(wCookieAdd, reqCookieAdd)
|
||||
require.Equal(t, http.StatusNoContent, wCookieAdd.Code)
|
||||
|
||||
require.NoError(t, db.First(&conversation, conversation.ID).Error)
|
||||
assert.Equal(t, "vip", conversation.Labels)
|
||||
|
||||
wCookieRemove := httptest.NewRecorder()
|
||||
reqCookieRemove, _ := http.NewRequest("DELETE", "/api/v1/widget/labels/vip", nil)
|
||||
reqCookieRemove.AddCookie(&http.Cookie{Name: "cw_conversation", Value: authToken})
|
||||
router.ServeHTTP(wCookieRemove, reqCookieRemove)
|
||||
require.Equal(t, http.StatusNoContent, wCookieRemove.Code)
|
||||
|
||||
require.NoError(t, db.First(&conversation, conversation.ID).Error)
|
||||
assert.Empty(t, conversation.Labels)
|
||||
}
|
||||
|
||||
func TestWidgetHandler_ChatwootEvents_InvalidWebsiteTokenReturnsNotFound(t *testing.T) {
|
||||
@@ -2130,6 +2403,25 @@ func TestWidgetHandler_PublicAPIInboxContactConversationMessageFlow(t *testing.T
|
||||
assert.Equal(t, "Public Visitor", contactResp["name"])
|
||||
assert.Equal(t, "visitor@public.test", contactResp["email"])
|
||||
assert.NotEmpty(t, contactResp["pubsub_token"])
|
||||
assertPublicContactFixtureShape(t, contactResp)
|
||||
|
||||
contactUpdateBody := map[string]any{
|
||||
"name": "Updated Public Visitor",
|
||||
"email": "Updated@Public.test",
|
||||
"phone_number": "+15550002222",
|
||||
}
|
||||
contactUpdateJSON, _ := json.Marshal(contactUpdateBody)
|
||||
w = httptest.NewRecorder()
|
||||
req, _ = http.NewRequest("PATCH", "/public/api/v1/inboxes/public-api-inbox/contacts/public-source-1", bytes.NewReader(contactUpdateJSON))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
router.ServeHTTP(w, req)
|
||||
|
||||
assert.Equal(t, http.StatusOK, w.Code)
|
||||
var contactUpdateResp map[string]any
|
||||
require.NoError(t, json.Unmarshal(w.Body.Bytes(), &contactUpdateResp))
|
||||
assertPublicContactFixtureShape(t, contactUpdateResp)
|
||||
assert.Equal(t, "Updated Public Visitor", contactUpdateResp["name"])
|
||||
assert.Equal(t, "updated@public.test", contactUpdateResp["email"])
|
||||
|
||||
w = httptest.NewRecorder()
|
||||
req, _ = http.NewRequest("POST", "/public/api/v1/inboxes/public-api-inbox/contacts/public-source-1/conversations", bytes.NewReader([]byte(`{"custom_attributes":{"topic":"sales"}}`)))
|
||||
@@ -2141,6 +2433,7 @@ func TestWidgetHandler_PublicAPIInboxContactConversationMessageFlow(t *testing.T
|
||||
require.NoError(t, json.Unmarshal(w.Body.Bytes(), &conversationResp))
|
||||
conversationID := strconv.FormatUint(uint64(conversationResp["id"].(float64)), 10)
|
||||
assert.Equal(t, "open", conversationResp["status"])
|
||||
assertPublicConversationFixtureShape(t, conversationResp)
|
||||
|
||||
messageBody := map[string]any{"content": "Hello from public API", "echo_id": "echo-1"}
|
||||
messageJSON, _ := json.Marshal(messageBody)
|
||||
@@ -2155,6 +2448,7 @@ func TestWidgetHandler_PublicAPIInboxContactConversationMessageFlow(t *testing.T
|
||||
messageID := strconv.FormatUint(uint64(messageResp["id"].(float64)), 10)
|
||||
assert.Equal(t, "Hello from public API", messageResp["content"])
|
||||
assert.Equal(t, "incoming", messageResp["message_type"])
|
||||
assertPublicMessageFixtureShape(t, messageResp)
|
||||
|
||||
w = httptest.NewRecorder()
|
||||
req, _ = http.NewRequest("GET", "/public/api/v1/inboxes/public-api-inbox/contacts/public-source-1/conversations/"+conversationID+"/messages", nil)
|
||||
@@ -2165,6 +2459,7 @@ func TestWidgetHandler_PublicAPIInboxContactConversationMessageFlow(t *testing.T
|
||||
require.NoError(t, json.Unmarshal(w.Body.Bytes(), &messagesResp))
|
||||
require.Len(t, messagesResp, 1)
|
||||
assert.Equal(t, "Hello from public API", messagesResp[0]["content"])
|
||||
assertPublicMessageFixtureShape(t, messagesResp[0])
|
||||
|
||||
updateBody := map[string]any{"submitted_values": []map[string]any{{"name": "email", "value": "visitor@public.test"}}}
|
||||
updateJSON, _ := json.Marshal(updateBody)
|
||||
@@ -2175,6 +2470,7 @@ func TestWidgetHandler_PublicAPIInboxContactConversationMessageFlow(t *testing.T
|
||||
|
||||
assert.Equal(t, http.StatusOK, w.Code)
|
||||
require.NoError(t, json.Unmarshal(w.Body.Bytes(), &messageResp))
|
||||
assertPublicMessageFixtureShape(t, messageResp)
|
||||
attrs := messageResp["content_attributes"].(map[string]any)
|
||||
assert.NotEmpty(t, attrs["submitted_values"])
|
||||
|
||||
@@ -2182,12 +2478,27 @@ func TestWidgetHandler_PublicAPIInboxContactConversationMessageFlow(t *testing.T
|
||||
req, _ = http.NewRequest("POST", "/public/api/v1/inboxes/public-api-inbox/contacts/public-source-1/conversations/"+conversationID+"/toggle_status", nil)
|
||||
router.ServeHTTP(w, req)
|
||||
assert.Equal(t, http.StatusOK, w.Code)
|
||||
require.NoError(t, json.Unmarshal(w.Body.Bytes(), &conversationResp))
|
||||
assertPublicConversationFixtureShape(t, conversationResp)
|
||||
assert.Equal(t, "resolved", conversationResp["status"])
|
||||
|
||||
w = httptest.NewRecorder()
|
||||
req, _ = http.NewRequest("GET", "/public/api/v1/inboxes/public-api-inbox/contacts/public-source-1/conversations/"+conversationID, nil)
|
||||
router.ServeHTTP(w, req)
|
||||
assert.Equal(t, http.StatusOK, w.Code)
|
||||
require.NoError(t, json.Unmarshal(w.Body.Bytes(), &conversationResp))
|
||||
assertPublicConversationFixtureShape(t, conversationResp)
|
||||
assert.Equal(t, "resolved", conversationResp["status"])
|
||||
|
||||
w = httptest.NewRecorder()
|
||||
req, _ = http.NewRequest("POST", "/public/api/v1/inboxes/public-api-inbox/contacts/public-source-1/conversations/"+conversationID+"/update_last_seen", nil)
|
||||
router.ServeHTTP(w, req)
|
||||
assert.Equal(t, http.StatusOK, w.Code)
|
||||
|
||||
var conversation model.Conversation
|
||||
require.NoError(t, db.Where("inbox_id = ? AND contact_id = ?", inbox.ID, contactResp["id"]).First(&conversation).Error)
|
||||
require.NotNil(t, conversation.ContactLastSeenAt)
|
||||
|
||||
w = httptest.NewRecorder()
|
||||
req, _ = http.NewRequest("POST", "/public/api/v1/inboxes/public-api-inbox/contacts/public-source-1/conversations/"+conversationID+"/toggle_typing", bytes.NewReader([]byte(`{"typing_status":"on"}`)))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
@@ -2521,3 +2832,68 @@ func TestWidgetHandler_PublicAPIMessageUpdate_CsatLocked(t *testing.T) {
|
||||
require.Equal(t, http.StatusUnprocessableEntity, w.Code)
|
||||
assert.Contains(t, w.Body.String(), "You cannot update the CSAT survey after 14 days")
|
||||
}
|
||||
|
||||
func assertWidgetConfigFixtureShape(t *testing.T, config map[string]interface{}) {
|
||||
t.Helper()
|
||||
for _, key := range []string{"auth_token", "website_token", "website_name", "welcome_tagline", "welcome_title", "widget_color"} {
|
||||
assert.Contains(t, config, key, "widget config should expose %s", key)
|
||||
}
|
||||
}
|
||||
|
||||
func assertWidgetContactFixtureShape(t *testing.T, contact map[string]interface{}) {
|
||||
t.Helper()
|
||||
for _, key := range []string{"has_email", "has_name", "has_phone_number", "id", "identifier"} {
|
||||
assert.Contains(t, contact, key, "widget contact should expose %s", key)
|
||||
}
|
||||
}
|
||||
|
||||
func assertWidgetConfigContactFixtureShape(t *testing.T, contact map[string]interface{}) {
|
||||
t.Helper()
|
||||
for _, key := range []string{"id", "identifier", "pubsub_token"} {
|
||||
assert.Contains(t, contact, key, "widget config contact should expose %s", key)
|
||||
}
|
||||
}
|
||||
|
||||
func assertWidgetMessageFixtureShape(t *testing.T, message map[string]interface{}) {
|
||||
t.Helper()
|
||||
for _, key := range []string{"content", "content_attributes", "content_type", "conversation_id", "created_at", "id", "inbox_id", "message_type", "private", "source_id"} {
|
||||
assert.Contains(t, message, key, "widget message should expose %s", key)
|
||||
}
|
||||
}
|
||||
|
||||
func assertWidgetAttachmentFixtureShape(t *testing.T, attachment map[string]interface{}) {
|
||||
t.Helper()
|
||||
for _, key := range []string{"account_id", "data_url", "extension", "file_size", "file_type", "height", "id", "message_id", "thumb_url", "width"} {
|
||||
assert.Contains(t, attachment, key, "widget attachment should expose %s", key)
|
||||
}
|
||||
assert.NotContains(t, attachment, "created_at")
|
||||
assert.NotContains(t, attachment, "updated_at")
|
||||
}
|
||||
|
||||
func assertWidgetCampaignFixtureShape(t *testing.T, campaign map[string]interface{}) {
|
||||
t.Helper()
|
||||
for _, key := range []string{"id", "message", "sender", "trigger_only_during_business_hours", "trigger_rules"} {
|
||||
assert.Contains(t, campaign, key, "widget campaign should expose %s", key)
|
||||
}
|
||||
}
|
||||
|
||||
func assertPublicContactFixtureShape(t *testing.T, contact map[string]any) {
|
||||
t.Helper()
|
||||
for _, key := range []string{"source_id", "pubsub_token", "id", "name", "email", "phone_number"} {
|
||||
assert.Contains(t, contact, key, "public contact should expose %s", key)
|
||||
}
|
||||
}
|
||||
|
||||
func assertPublicConversationFixtureShape(t *testing.T, conversation map[string]any) {
|
||||
t.Helper()
|
||||
for _, key := range []string{"id", "uuid", "inbox_id", "contact_last_seen_at", "status", "agent_last_seen_at", "contact", "messages"} {
|
||||
assert.Contains(t, conversation, key, "public conversation should expose %s", key)
|
||||
}
|
||||
}
|
||||
|
||||
func assertPublicMessageFixtureShape(t *testing.T, message map[string]any) {
|
||||
t.Helper()
|
||||
for _, key := range []string{"id", "content", "message_type", "content_type", "content_attributes", "created_at", "conversation_id"} {
|
||||
assert.Contains(t, message, key, "public message should expose %s", key)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user