Align GoChat with Chatwoot frontend contracts

This commit is contained in:
2026-06-13 22:13:32 +08:00
parent 71abf58636
commit d884fdda0a
162 changed files with 9825 additions and 528 deletions
+91 -20
View File
@@ -126,6 +126,7 @@ func (s *ProfileHandlerTestSuite) buildRouter() *gin.Engine {
profile.GET("", s.handler.Get)
profile.PUT("", s.handler.Update)
profile.PUT("/avatar", s.handler.UpdateAvatar)
profile.DELETE("/avatar", s.handler.DeleteAvatar)
profile.POST("/availability", s.handler.SetAvailability)
profile.POST("/auto_offline", s.handler.SetAutoOffline)
profile.PUT("/set_active_account", s.handler.SetActiveAccount)
@@ -200,6 +201,44 @@ func (s *ProfileHandlerTestSuite) firstAccountFromProfile(payload map[string]int
return account
}
func (s *ProfileHandlerTestSuite) assertChatwootProfileUserFixture(payload map[string]interface{}, expectedAvailability string, expectedAutoOffline bool) map[string]interface{} {
s.T().Helper()
assert.Equal(s.T(), float64(s.userID), payload["id"])
assert.Equal(s.T(), "ProfileUser", payload["name"])
assert.Equal(s.T(), "profile@example.com", payload["email"])
assert.Equal(s.T(), "", payload["uid"])
assert.Equal(s.T(), "Profile Display", payload["available_name"])
assert.Equal(s.T(), "Profile Display", payload["display_name"])
assert.Equal(s.T(), "", payload["avatar_url"])
assert.Equal(s.T(), "User", payload["type"])
assert.Equal(s.T(), "email", payload["provider"])
assert.Equal(s.T(), "pubsub-profile-user", payload["pubsub_token"])
assert.Equal(s.T(), "Regards", payload["message_signature"])
assert.Equal(s.T(), "profile-token-1", payload["access_token"])
assert.Equal(s.T(), "administrator", payload["role"])
assert.Equal(s.T(), map[string]interface{}{}, payload["custom_attributes"])
assert.Equal(s.T(), map[string]interface{}{}, payload["ui_settings"])
assert.Equal(s.T(), false, payload["confirmed"])
assert.Equal(s.T(), float64(s.accountID), payload["account_id"])
assert.Nil(s.T(), payload["inviter_id"])
assert.NotContains(s.T(), payload, "hmac_identifier")
account := s.firstAccountFromProfile(payload)
assert.Equal(s.T(), float64(s.accountID), account["id"])
assert.Equal(s.T(), "TestAccount", account["name"])
assert.Equal(s.T(), "active", account["status"])
assert.Equal(s.T(), "profile", account["onboarding_step"])
assert.Equal(s.T(), "administrator", account["role"])
assert.Equal(s.T(), expectedAvailability, account["availability"])
assert.Equal(s.T(), expectedAvailability, account["availability_status"])
assert.Equal(s.T(), expectedAutoOffline, account["auto_offline"])
assert.Equal(s.T(), []interface{}{"administrator"}, account["permissions"])
assert.Nil(s.T(), account["custom_role_id"])
assert.Nil(s.T(), account["custom_role"])
return account
}
// ===================== Get Profile =====================
func (s *ProfileHandlerTestSuite) TestGet_Success() {
@@ -210,24 +249,7 @@ func (s *ProfileHandlerTestSuite) TestGet_Success() {
assert.Equal(s.T(), http.StatusOK, w.Code)
dataMap := s.decodeProfileBody(w)
assert.Equal(s.T(), "ProfileUser", dataMap["name"])
assert.Equal(s.T(), "profile@example.com", dataMap["email"])
assert.Equal(s.T(), "profile-token-1", dataMap["access_token"])
assert.Equal(s.T(), "Profile Display", dataMap["available_name"])
assert.Equal(s.T(), "Regards", dataMap["message_signature"])
assert.Equal(s.T(), "pubsub-profile-user", dataMap["pubsub_token"])
assert.NotContains(s.T(), dataMap, "hmac_identifier")
assert.Equal(s.T(), "administrator", dataMap["role"])
accounts, ok := dataMap["accounts"].([]interface{})
assert.True(s.T(), ok)
if assert.Len(s.T(), accounts, 1) {
account := accounts[0].(map[string]interface{})
assert.Equal(s.T(), "TestAccount", account["name"])
assert.Equal(s.T(), "offline", account["availability"])
assert.Equal(s.T(), "offline", account["availability_status"])
assert.Equal(s.T(), true, account["auto_offline"])
assert.Equal(s.T(), []interface{}{"administrator"}, account["permissions"])
}
s.assertChatwootProfileUserFixture(dataMap, "offline", true)
}
func (s *ProfileHandlerTestSuite) TestGet_CustomRolePermissions() {
@@ -694,6 +716,22 @@ func (s *ProfileHandlerTestSuite) TestUpdateAvatar_NilService() {
// Nil service causes panic — validates catastrophic failure path
}
func (s *ProfileHandlerTestSuite) TestDeleteAvatar_ReturnsChatwootUserSerializer() {
s.Require().NoError(s.db.Model(&model.User{}).Where("id = ?", s.userID).Update("avatar_url", "https://cdn.example.com/current-avatar.png").Error)
req, _ := http.NewRequest(http.MethodDelete, "/api/v1/profile/avatar", nil)
w := httptest.NewRecorder()
s.router.ServeHTTP(w, req)
assert.Equal(s.T(), http.StatusOK, w.Code)
dataMap := s.decodeProfileBody(w)
assert.Equal(s.T(), "", dataMap["avatar_url"])
var user model.User
s.Require().NoError(s.db.First(&user, s.userID).Error)
assert.Equal(s.T(), "", user.AvatarURL)
}
// ===================== Chatwoot Profile Serializer Fixtures =====================
func (s *ProfileHandlerTestSuite) TestSetAvailability_ReturnsChatwootUserSerializer() {
@@ -712,11 +750,14 @@ func (s *ProfileHandlerTestSuite) TestSetAvailability_ReturnsChatwootUserSeriali
assert.Equal(s.T(), http.StatusOK, w.Code)
payload := s.decodeProfileBody(w)
assert.Equal(s.T(), "ProfileUser", payload["name"])
assert.Equal(s.T(), "administrator", payload["role"])
s.assertChatwootProfileUserFixture(payload, "online", true)
account := s.firstAccountFromProfile(payload)
assert.Equal(s.T(), "online", account["availability"])
assert.Equal(s.T(), "online", account["availability_status"])
var accountUser model.AccountUser
s.Require().NoError(s.db.Where("account_id = ? AND user_id = ?", s.accountID, s.userID).First(&accountUser).Error)
assert.Equal(s.T(), "online", accountUser.Availability)
}
func (s *ProfileHandlerTestSuite) TestSetAutoOffline_ReturnsChatwootUserSerializer() {
@@ -735,8 +776,38 @@ func (s *ProfileHandlerTestSuite) TestSetAutoOffline_ReturnsChatwootUserSerializ
assert.Equal(s.T(), http.StatusOK, w.Code)
payload := s.decodeProfileBody(w)
s.assertChatwootProfileUserFixture(payload, "offline", false)
account := s.firstAccountFromProfile(payload)
assert.Equal(s.T(), false, account["auto_offline"])
var accountUser model.AccountUser
s.Require().NoError(s.db.Where("account_id = ? AND user_id = ?", s.accountID, s.userID).First(&accountUser).Error)
assert.False(s.T(), accountUser.AutoOffline)
}
func (s *ProfileHandlerTestSuite) TestSetActiveAccount_UpdatesMembershipActiveAt() {
var before model.AccountUser
s.Require().NoError(s.db.Where("account_id = ? AND user_id = ?", s.accountID, s.userID).First(&before).Error)
s.Require().Nil(before.ActiveAt)
body := map[string]interface{}{
"profile": map[string]interface{}{
"account_id": s.accountID,
},
}
b, _ := json.Marshal(body)
req, _ := http.NewRequest("PUT", "/api/v1/profile/set_active_account", bytes.NewReader(b))
req.Header.Set("Content-Type", "application/json")
w := httptest.NewRecorder()
s.router.ServeHTTP(w, req)
assert.Equal(s.T(), http.StatusNoContent, w.Code)
assert.Empty(s.T(), w.Body.String())
var after model.AccountUser
s.Require().NoError(s.db.Where("account_id = ? AND user_id = ?", s.accountID, s.userID).First(&after).Error)
s.Require().NotNil(after.ActiveAt)
assert.WithinDuration(s.T(), time.Now(), *after.ActiveAt, 5*time.Second)
}
func (s *ProfileHandlerTestSuite) TestResetAccessToken_RegeneratesTokenInChatwootUserSerializer() {