feat(public): implement chatwoot csat survey flow

This commit is contained in:
2026-06-04 22:57:15 +08:00
parent 8cd564e1da
commit 79042add01
8 changed files with 474 additions and 39 deletions
@@ -22,6 +22,7 @@ import (
"gorm.io/gorm"
"gorm.io/gorm/logger"
"github.com/gochat/gochat/internal/automation"
"github.com/gochat/gochat/internal/campaign"
"github.com/gochat/gochat/internal/model"
channelmodel "github.com/gochat/gochat/internal/model/channel"
@@ -59,6 +60,7 @@ func setupWidgetHandlerTest(t *testing.T) (*gorm.DB, *gin.Engine, *WidgetHandler
&model.Message{},
&model.Attachment{},
&model.DirectUpload{},
&automation.CsatSurveyResponse{},
&model.WidgetThemeConfig{},
&model.PreChatForm{},
&model.WidgetFileUpload{},
@@ -1286,3 +1288,62 @@ func TestWidgetHandler_PublicAPIInboxContactConversationMessageFlow(t *testing.T
router.ServeHTTP(w, req)
assert.Equal(t, http.StatusOK, w.Code)
}
func TestWidgetHandler_PublicAPIMessageUpdate_CsatSubmission(t *testing.T) {
db, router, _ := setupWidgetHandlerTest(t)
account, inbox, _ := seedPublicAPIInbox(t, db)
contact := &model.Contact{AccountID: account.ID, Name: "CSAT Public Visitor"}
require.NoError(t, db.Create(contact).Error)
contactInbox := &model.ContactInbox{ContactID: contact.ID, InboxID: inbox.ID, SourceID: "csat-source", PubsubToken: "csat-pubsub"}
require.NoError(t, db.Create(contactInbox).Error)
conversation := &model.Conversation{AccountID: account.ID, InboxID: inbox.ID, ContactID: contact.ID, ContactInboxID: &contactInbox.ID, Status: "resolved", ChannelType: inbox.ChannelType, Channel: inbox.ChannelType}
require.NoError(t, db.Create(conversation).Error)
message := &model.Message{
ConversationID: conversation.ID,
AccountID: account.ID,
InboxID: inbox.ID,
Content: "Rate this conversation",
ContentType: "input_csat",
MessageType: "outgoing",
ContentAttributes: datatypes.JSON(`{}`),
}
require.NoError(t, db.Create(message).Error)
body := `{"submitted_values":[{"csat_survey_response":{"rating":3,"feedback_message":"Okay"}}]}`
w := httptest.NewRecorder()
url := "/public/api/v1/inboxes/public-api-inbox/contacts/csat-source/conversations/" + strconv.FormatUint(uint64(conversation.ID), 10) + "/messages/" + strconv.FormatUint(uint64(message.ID), 10)
req, _ := http.NewRequest("PATCH", url, bytes.NewReader([]byte(body)))
req.Header.Set("Content-Type", "application/json")
router.ServeHTTP(w, req)
require.Equal(t, http.StatusOK, w.Code)
var stored automation.CsatSurveyResponse
require.NoError(t, db.Where("message_id = ?", message.ID).First(&stored).Error)
assert.Equal(t, 3, stored.Rating)
assert.Equal(t, "Okay", stored.FeedbackMessage)
assert.Equal(t, contact.ID, stored.ContactID)
}
func TestWidgetHandler_PublicAPIMessageUpdate_CsatLocked(t *testing.T) {
db, router, _ := setupWidgetHandlerTest(t)
account, inbox, _ := seedPublicAPIInbox(t, db)
contact := &model.Contact{AccountID: account.ID, Name: "Late CSAT Visitor"}
require.NoError(t, db.Create(contact).Error)
contactInbox := &model.ContactInbox{ContactID: contact.ID, InboxID: inbox.ID, SourceID: "late-csat-source", PubsubToken: "late-csat-pubsub"}
require.NoError(t, db.Create(contactInbox).Error)
conversation := &model.Conversation{AccountID: account.ID, InboxID: inbox.ID, ContactID: contact.ID, ContactInboxID: &contactInbox.ID, Status: "resolved", ChannelType: inbox.ChannelType, Channel: inbox.ChannelType}
require.NoError(t, db.Create(conversation).Error)
message := &model.Message{ConversationID: conversation.ID, AccountID: account.ID, InboxID: inbox.ID, Content: "Rate this conversation", ContentType: "input_csat", MessageType: "outgoing", ContentAttributes: datatypes.JSON(`{}`)}
require.NoError(t, db.Create(message).Error)
old := time.Now().AddDate(0, 0, -15)
require.NoError(t, db.Model(message).Updates(map[string]any{"created_at": old, "updated_at": old}).Error)
body := `{"submitted_values":[{"csat_survey_response":{"rating":5,"feedback_message":"Late"}}]}`
w := httptest.NewRecorder()
url := "/public/api/v1/inboxes/public-api-inbox/contacts/late-csat-source/conversations/" + strconv.FormatUint(uint64(conversation.ID), 10) + "/messages/" + strconv.FormatUint(uint64(message.ID), 10)
req, _ := http.NewRequest("PATCH", url, bytes.NewReader([]byte(body)))
req.Header.Set("Content-Type", "application/json")
router.ServeHTTP(w, req)
require.Equal(t, http.StatusUnprocessableEntity, w.Code)
assert.Contains(t, w.Body.String(), "You cannot update the CSAT survey after 14 days")
}