feat(widget): cover campaigns events and labels

This commit is contained in:
2026-06-04 21:45:49 +08:00
parent 67a6ae762e
commit f3b450905f
9 changed files with 434 additions and 22 deletions
+142 -1
View File
@@ -19,6 +19,7 @@ import (
"gorm.io/gorm"
"gorm.io/gorm/logger"
"github.com/gochat/gochat/internal/campaign"
"github.com/gochat/gochat/internal/model"
"github.com/gochat/gochat/internal/repository"
"github.com/gochat/gochat/internal/service"
@@ -56,6 +57,11 @@ func setupWidgetHandlerTest(t *testing.T) (*gorm.DB, *gin.Engine, *WidgetHandler
&model.PreChatForm{},
&model.WidgetFileUpload{},
&model.WidgetOfflineMessage{},
&model.User{},
&model.AccountUser{},
&model.InboxMember{},
&model.Tag{},
&campaign.Campaign{},
))
t.Cleanup(func() {
@@ -72,11 +78,14 @@ func setupWidgetHandlerTest(t *testing.T) (*gorm.DB, *gin.Engine, *WidgetHandler
preChatFormRepo := repository.NewPreChatFormRepo(db)
fileUploadRepo := repository.NewWidgetFileUploadRepo(db)
offlineMsgRepo := repository.NewWidgetOfflineMessageRepo(db)
inboxMemberRepo := repository.NewInboxMemberRepo(db)
tagRepo := repository.NewTagRepo(db)
campaignRepo := repository.NewCampaignRepo(db)
widgetSvc := service.NewWidgetService(
inboxRepo, contactRepo, contactInboxRepo, conversationRepo,
messageRepo, &noopTypingIndicatorWidget{}, themeConfigRepo, preChatFormRepo,
fileUploadRepo, offlineMsgRepo,
fileUploadRepo, offlineMsgRepo, inboxMemberRepo, tagRepo, campaignRepo,
)
handler := NewHandler(widgetSvc)
@@ -101,6 +110,11 @@ func setupWidgetHandlerTest(t *testing.T) (*gorm.DB, *gin.Engine, *WidgetHandler
chatwootWidgetGroup.POST("/messages", handler.SendMessage)
chatwootWidgetGroup.POST("/conversations", handler.CreateConversation)
chatwootWidgetGroup.GET("/contact", handler.GetContact)
chatwootWidgetGroup.GET("/campaigns", handler.ListCampaigns)
chatwootWidgetGroup.GET("/inbox_members", handler.ListInboxMembers)
chatwootWidgetGroup.POST("/events", handler.CreateEvent)
chatwootWidgetGroup.POST("/labels", handler.AddLabel)
chatwootWidgetGroup.DELETE("/labels/:label_id", handler.RemoveLabel)
}
return db, router, handler
@@ -278,6 +292,133 @@ func TestWidgetHandler_ChatwootMessages_AuthTokenAndNestedPayload(t *testing.T)
require.Equal(t, http.StatusOK, wContact.Code)
}
func TestWidgetHandler_ChatwootInboxMembers_Success(t *testing.T) {
db, router, _ := setupWidgetHandlerTest(t)
_, inbox := seedWidgetHandlerData(t, db)
user := &model.User{
AccountID: inbox.AccountID,
Name: "Ada Agent",
Email: "ada@handler.test",
Password: "secret",
AvatarURL: "https://example.test/ada.png",
Active: true,
}
require.NoError(t, db.Create(user).Error)
require.NoError(t, db.Create(&model.InboxMember{
InboxID: inbox.ID,
UserID: user.ID,
Role: "agent",
AvailabilityStatus: "online",
}).Error)
w := httptest.NewRecorder()
req, _ := http.NewRequest("GET", "/api/v1/widget/inbox_members?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))
payload := resp["payload"].([]interface{})
require.Len(t, payload, 1)
member := payload[0].(map[string]interface{})
assert.Equal(t, "Ada Agent", member["name"])
assert.Equal(t, "online", member["availability_status"])
}
func TestWidgetHandler_ChatwootCampaigns_Success(t *testing.T) {
db, router, _ := setupWidgetHandlerTest(t)
_, inbox := seedWidgetHandlerData(t, db)
require.NoError(t, db.Create(&campaign.Campaign{
AccountID: inbox.AccountID,
InboxID: inbox.ID,
DisplayID: 42,
Title: "Welcome",
Message: "Welcome to support",
CampaignType: campaign.CampaignTypeOngoing,
TriggerRules: `{"url":"https://example.test","time_on_page":10}`,
Enabled: true,
}).Error)
disabledCampaign := &campaign.Campaign{
AccountID: inbox.AccountID,
InboxID: inbox.ID,
DisplayID: 43,
Title: "Disabled",
Message: "Hidden",
CampaignType: campaign.CampaignTypeOngoing,
Enabled: false,
}
require.NoError(t, db.Create(disabledCampaign).Error)
require.NoError(t, db.Model(disabledCampaign).Update("enabled", false).Error)
w := httptest.NewRecorder()
req, _ := http.NewRequest("GET", "/api/v1/widget/campaigns?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))
require.Len(t, resp, 1)
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{})
assert.Equal(t, "https://example.test", rules["url"])
}
func TestWidgetHandler_ChatwootEventsAndLabels(t *testing.T) {
db, router, _ := setupWidgetHandlerTest(t)
_, inbox := seedWidgetHandlerData(t, db)
require.NoError(t, db.Create(&model.Tag{AccountID: inbox.AccountID, Name: "vip"}).Error)
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 := map[string]interface{}{"message": map[string]interface{}{"content": "Label me"}}
bodyJSON, _ := json.Marshal(body)
wMessage := httptest.NewRecorder()
reqMessage, _ := http.NewRequest("POST", "/api/v1/widget/messages", bytes.NewReader(bodyJSON))
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)
wEvent := httptest.NewRecorder()
eventBody, _ := json.Marshal(map[string]interface{}{"name": "widget.opened", "event_info": map[string]interface{}{"source": "test"}})
reqEvent, _ := http.NewRequest("POST", "/api/v1/widget/events?website_token=handler_ws_token_123", bytes.NewReader(eventBody))
reqEvent.Header.Set("Content-Type", "application/json")
reqEvent.Header.Set("X-Auth-Token", authToken)
router.ServeHTTP(wEvent, reqEvent)
require.Equal(t, http.StatusNoContent, wEvent.Code)
wAdd := httptest.NewRecorder()
labelBody, _ := json.Marshal(map[string]interface{}{"label": "vip"})
reqAdd, _ := http.NewRequest("POST", "/api/v1/widget/labels", bytes.NewReader(labelBody))
reqAdd.Header.Set("Content-Type", "application/json")
reqAdd.Header.Set("X-Auth-Token", authToken)
router.ServeHTTP(wAdd, reqAdd)
require.Equal(t, http.StatusNoContent, wAdd.Code)
var conversation model.Conversation
require.NoError(t, db.Where("inbox_id = ?", inbox.ID).First(&conversation).Error)
assert.Equal(t, "vip", conversation.Labels)
wRemove := httptest.NewRecorder()
reqRemove, _ := http.NewRequest("DELETE", "/api/v1/widget/labels/vip", nil)
reqRemove.Header.Set("X-Auth-Token", authToken)
router.ServeHTTP(wRemove, reqRemove)
require.Equal(t, http.StatusNoContent, wRemove.Code)
require.NoError(t, db.First(&conversation, conversation.ID).Error)
assert.Empty(t, conversation.Labels)
}
// ========== SendMessage Handler Tests ==========
func TestWidgetHandler_SendMessage_Success(t *testing.T) {