feat(settings): align assignable agents payloads

This commit is contained in:
2026-06-05 23:54:37 +08:00
parent dc09036676
commit 5efa1955e5
4 changed files with 137 additions and 128 deletions
@@ -80,5 +80,39 @@ func (h *AssignableAgentHandler) List(c *gin.Context) {
return
}
response.OK(c, agents)
c.JSON(http.StatusOK, gin.H{"payload": serializeAssignableAgents(accountID, agents)})
}
func serializeAssignableAgents(accountID uint, agents []service.AssignableAgentDTO) []map[string]any {
payload := make([]map[string]any, 0, len(agents))
for i := range agents {
agent := agents[i]
availableName := agent.DisplayName
if availableName == "" {
availableName = agent.Name
}
provider := agent.Provider
if provider == "" {
provider = "email"
}
customRoleID := any(nil)
if agent.CustomRoleID != 0 {
customRoleID = agent.CustomRoleID
}
payload = append(payload, map[string]any{
"id": agent.ID,
"account_id": accountID,
"availability_status": agent.AvailabilityStatus,
"auto_offline": agent.AutoOffline,
"confirmed": agent.Confirmed,
"email": agent.Email,
"provider": provider,
"available_name": availableName,
"name": agent.Name,
"role": agent.Role,
"thumbnail": agent.AvatarURL,
"custom_role_id": customRoleID,
})
}
return payload
}
@@ -27,14 +27,14 @@ type AssignableAgentHandlerTestSuite struct {
router *gin.Engine
handler *AssignableAgentHandler
account *model.Account
inbox1 *model.Inbox
inbox2 *model.Inbox
inbox3 *model.Inbox
user1 *model.User // agent in inbox1
user2 *model.User // agent in inbox1 + inbox2
user3 *model.User // administrator of account
user4 *model.User // agent only in inbox2
account *model.Account
inbox1 *model.Inbox
inbox2 *model.Inbox
inbox3 *model.Inbox
user1 *model.User // agent in inbox1
user2 *model.User // agent in inbox1 + inbox2
user3 *model.User // administrator of account
user4 *model.User // agent only in inbox2
}
func (s *AssignableAgentHandlerTestSuite) SetupSuite() {
@@ -168,6 +168,15 @@ func (s *AssignableAgentHandlerTestSuite) TearDownTest() {
s.reseedData()
}
func (s *AssignableAgentHandlerTestSuite) decodeAssignablePayload(w *httptest.ResponseRecorder) []interface{} {
var resp map[string]interface{}
s.Require().NoError(json.Unmarshal(w.Body.Bytes(), &resp))
s.Require().NotContains(resp, "success")
payload, ok := resp["payload"].([]interface{})
s.Require().True(ok, "expected Chatwoot payload array, got %v", resp)
return payload
}
func (s *AssignableAgentHandlerTestSuite) reseedData() {
// Re-create the AccountUser + InboxMember associations
s.Require().NoError(s.db.Create(&model.AccountUser{
@@ -211,13 +220,16 @@ func (s *AssignableAgentHandlerTestSuite) TestList_SingleInbox_ReturnsInboxMembe
s.Equal(http.StatusOK, w.Code)
var resp map[string]interface{}
s.NoError(json.Unmarshal(w.Body.Bytes(), &resp))
s.True(resp["success"].(bool))
data := resp["data"].([]interface{})
data := s.decodeAssignablePayload(w)
// Should include user1, user2 (inbox members) + user3 (administrator) = 3
s.Len(data, 3)
agent := data[0].(map[string]interface{})
s.Contains(agent, "availability_status")
s.Contains(agent, "available_name")
s.Contains(agent, "auto_offline")
s.Contains(agent, "confirmed")
s.Contains(agent, "thumbnail")
s.Contains(agent, "custom_role_id")
}
func (s *AssignableAgentHandlerTestSuite) TestList_InboxWithOnlyOneMember() {
@@ -229,11 +241,7 @@ func (s *AssignableAgentHandlerTestSuite) TestList_InboxWithOnlyOneMember() {
s.Equal(http.StatusOK, w.Code)
var resp map[string]interface{}
s.NoError(json.Unmarshal(w.Body.Bytes(), &resp))
s.True(resp["success"].(bool))
data := resp["data"].([]interface{})
data := s.decodeAssignablePayload(w)
// user2, user4 (inbox2 members) + user3 (administrator) = 3
s.Len(data, 3)
}
@@ -247,11 +255,7 @@ func (s *AssignableAgentHandlerTestSuite) TestList_InboxWithNoMembers_ReturnsOnl
s.Equal(http.StatusOK, w.Code)
var resp map[string]interface{}
s.NoError(json.Unmarshal(w.Body.Bytes(), &resp))
s.True(resp["success"].(bool))
data := resp["data"].([]interface{})
data := s.decodeAssignablePayload(w)
// Only administrator user3
s.Len(data, 1)
}
@@ -268,11 +272,7 @@ func (s *AssignableAgentHandlerTestSuite) TestList_MultipleInboxIDsQueryParam()
s.Equal(http.StatusOK, w.Code)
var resp map[string]interface{}
s.NoError(json.Unmarshal(w.Body.Bytes(), &resp))
s.True(resp["success"].(bool))
data := resp["data"].([]interface{})
data := s.decodeAssignablePayload(w)
// Intersection: user2. Admin: user3. Total: 2
s.Len(data, 2)
}
@@ -289,11 +289,7 @@ func (s *AssignableAgentHandlerTestSuite) TestList_MultipleInboxIDsQueryParams_N
s.Equal(http.StatusOK, w.Code)
var resp map[string]interface{}
s.NoError(json.Unmarshal(w.Body.Bytes(), &resp))
s.True(resp["success"].(bool))
data := resp["data"].([]interface{})
data := s.decodeAssignablePayload(w)
// Empty intersection + admin user3
s.Len(data, 1)
}
@@ -309,11 +305,7 @@ func (s *AssignableAgentHandlerTestSuite) TestList_QueryParamSameAsPrimaryInboxI
s.Equal(http.StatusOK, w.Code)
var resp map[string]interface{}
s.NoError(json.Unmarshal(w.Body.Bytes(), &resp))
s.True(resp["success"].(bool))
data := resp["data"].([]interface{})
data := s.decodeAssignablePayload(w)
// Same as single inbox1: user1, user2, user3
s.Len(data, 3)
}
@@ -332,11 +324,7 @@ func (s *AssignableAgentHandlerTestSuite) TestList_MultipleAdditionalInboxIDs()
s.Equal(http.StatusOK, w.Code)
var resp map[string]interface{}
s.NoError(json.Unmarshal(w.Body.Bytes(), &resp))
s.True(resp["success"].(bool))
data := resp["data"].([]interface{})
data := s.decodeAssignablePayload(w)
s.Len(data, 1)
}
@@ -398,11 +386,7 @@ func (s *AssignableAgentHandlerTestSuite) TestList_InvalidQueryParamInboxIDs_Ign
s.Equal(http.StatusOK, w.Code)
var resp map[string]interface{}
s.NoError(json.Unmarshal(w.Body.Bytes(), &resp))
s.True(resp["success"].(bool))
data := resp["data"].([]interface{})
data := s.decodeAssignablePayload(w)
// Same as just inbox1: user1, user2, user3
s.Len(data, 3)
}
@@ -417,11 +401,7 @@ func (s *AssignableAgentHandlerTestSuite) TestList_NonExistentInboxID() {
s.Equal(http.StatusOK, w.Code)
var resp map[string]interface{}
s.NoError(json.Unmarshal(w.Body.Bytes(), &resp))
s.True(resp["success"].(bool))
data := resp["data"].([]interface{})
data := s.decodeAssignablePayload(w)
// No inbox members, only administrators: user3
s.Len(data, 1)
}
@@ -437,11 +417,7 @@ func (s *AssignableAgentHandlerTestSuite) TestList_NonExistentAccountID() {
s.Equal(http.StatusOK, w.Code)
var resp map[string]interface{}
s.NoError(json.Unmarshal(w.Body.Bytes(), &resp))
s.True(resp["success"].(bool))
data := resp["data"].([]interface{})
data := s.decodeAssignablePayload(w)
// Inbox members of inbox1: user1, user2. No admins for account 99999.
s.Len(data, 2)
}
@@ -543,10 +519,8 @@ func (s *AssignableAgentHandlerTestSuite) TestList_NilService_PanicRecovered() {
s.Equal(http.StatusUnprocessableEntity, w.Code)
}
func (s *AssignableAgentHandlerTestSuite) TestList_ResponseStructure() {
// Verify the response structure matches: {success: true, data: [...]}
// Verify the response structure matches Chatwoot: {payload: [...]}
w := httptest.NewRecorder()
req, _ := http.NewRequest("GET",
fmt.Sprintf("/api/v1/accounts/%d/inboxes/%d/assignable_agents", s.account.ID, s.inbox1.ID), nil)
@@ -557,10 +531,9 @@ func (s *AssignableAgentHandlerTestSuite) TestList_ResponseStructure() {
var resp map[string]interface{}
s.NoError(json.Unmarshal(w.Body.Bytes(), &resp))
// Must have "success" key set to true
s.True(resp["success"].(bool))
// Must have "data" key
s.NotNil(resp["data"])
s.NotContains(resp, "success")
s.NotContains(resp, "data")
s.NotNil(resp["payload"])
// No "meta" key for non-paginated response
s.Nil(resp["meta"])
// No "error" key for success response
@@ -581,11 +554,7 @@ func (s *AssignableAgentHandlerTestSuite) TestList_AdministratorDeduplication()
s.Equal(http.StatusOK, w.Code)
var resp map[string]interface{}
s.NoError(json.Unmarshal(w.Body.Bytes(), &resp))
s.True(resp["success"].(bool))
data := resp["data"].([]interface{})
data := s.decodeAssignablePayload(w)
// user1, user2 (inbox members), user3 (admin AND inbox member) - deduplicated
// Should be 3, not 4 (user3 counted once)
s.Len(data, 3)
@@ -609,11 +578,7 @@ func (s *AssignableAgentHandlerTestSuite) TestList_AccountWithNoAdministrators()
s.Equal(http.StatusOK, w.Code)
var resp map[string]interface{}
s.NoError(json.Unmarshal(w.Body.Bytes(), &resp))
s.True(resp["success"].(bool))
data := resp["data"].([]interface{})
data := s.decodeAssignablePayload(w)
// Only inbox member user1 (no administrators added)
s.Len(data, 1)
}
@@ -627,9 +592,7 @@ func (s *AssignableAgentHandlerTestSuite) TestList_LargeAccountID() {
s.Equal(http.StatusOK, w.Code)
var resp map[string]interface{}
s.NoError(json.Unmarshal(w.Body.Bytes(), &resp))
s.True(resp["success"].(bool))
s.decodeAssignablePayload(w)
}
func (s *AssignableAgentHandlerTestSuite) TestList_NegativeAccountID_ParseError() {
@@ -705,11 +668,7 @@ func (s *AssignableAgentHandlerTestSuite) TestList_TwoInboxesWithPartialIntersec
s.Equal(http.StatusOK, w.Code)
var resp map[string]interface{}
s.NoError(json.Unmarshal(w.Body.Bytes(), &resp))
s.True(resp["success"].(bool))
data := resp["data"].([]interface{})
data := s.decodeAssignablePayload(w)
s.Len(data, 2)
}
@@ -728,15 +687,11 @@ func (s *AssignableAgentHandlerTestSuite) TestList_ThreeInboxesIntersectionWithT
s.Equal(http.StatusOK, w.Code)
var resp map[string]interface{}
s.NoError(json.Unmarshal(w.Body.Bytes(), &resp))
s.True(resp["success"].(bool))
data := resp["data"].([]interface{})
data := s.decodeAssignablePayload(w)
s.Len(data, 2)
}
// Run the test suite
func TestAssignableAgentHandlerSuite(t *testing.T) {
suite.Run(t, new(AssignableAgentHandlerTestSuite))
}
}