Align GoChat with Chatwoot frontend contracts
This commit is contained in:
@@ -26,7 +26,7 @@ type LiveReportHandlerTestSuite struct {
|
||||
|
||||
func (s *LiveReportHandlerTestSuite) SetupSuite() {
|
||||
s.db, _ = gorm.Open(sqlite.Open(":memory:"), &gorm.Config{})
|
||||
s.db.AutoMigrate(&model.Account{}, &model.Team{}, &model.Conversation{}, &model.ReportingEventsRollup{})
|
||||
s.db.AutoMigrate(&model.Account{}, &model.Team{}, &model.Conversation{}, &model.Message{}, &model.ReportingEventsRollup{})
|
||||
|
||||
anSvc := service.NewAnalyticsService(
|
||||
repository.NewReportingEventRepo(s.db),
|
||||
@@ -38,10 +38,13 @@ func (s *LiveReportHandlerTestSuite) SetupSuite() {
|
||||
r := gin.New()
|
||||
r.GET("/api/v1/accounts/:account_id/live_reports/conversation_metrics", s.handler.ConversationMetrics)
|
||||
r.GET("/api/v1/accounts/:account_id/live_reports/grouped_conversation_metrics", s.handler.GroupedConversationMetrics)
|
||||
r.GET("/api/v2/accounts/:account_id/live_reports/conversation_metrics", s.handler.ConversationMetrics)
|
||||
r.GET("/api/v2/accounts/:account_id/live_reports/grouped_conversation_metrics", s.handler.GroupedConversationMetrics)
|
||||
s.router = r
|
||||
}
|
||||
|
||||
func (s *LiveReportHandlerTestSuite) SetupTest() {
|
||||
s.db.Exec("DELETE FROM messages")
|
||||
s.db.Exec("DELETE FROM conversations")
|
||||
s.db.Exec("DELETE FROM teams")
|
||||
}
|
||||
@@ -71,6 +74,141 @@ func (s *LiveReportHandlerTestSuite) TestConversationMetrics_Success() {
|
||||
s.Equal(float64(0), body["pending"])
|
||||
}
|
||||
|
||||
func (s *LiveReportHandlerTestSuite) TestAPIV2LiveReports_ChatwootFrontendPayloadShapes() {
|
||||
team := model.Team{AccountID: 1, Name: "Support"}
|
||||
otherTeam := model.Team{AccountID: 1, Name: "Other"}
|
||||
s.Require().NoError(s.db.Create(&team).Error)
|
||||
s.Require().NoError(s.db.Create(&otherTeam).Error)
|
||||
agentID := uint(7)
|
||||
otherAgentID := uint(8)
|
||||
firstReply := int64(1760000000)
|
||||
waitingSince := int64(1760000100)
|
||||
teamOpen := model.Conversation{AccountID: 1, TeamID: &team.ID, AssigneeID: &agentID, Status: string(model.ConversationStatusOpen), ChannelType: "web_widget", Channel: "web_widget"}
|
||||
teamReplied := model.Conversation{AccountID: 1, TeamID: &team.ID, AssigneeID: &agentID, Status: string(model.ConversationStatusOpen), ChannelType: "web_widget", Channel: "web_widget", FirstReplyCreatedAt: &firstReply}
|
||||
teamWaiting := model.Conversation{AccountID: 1, TeamID: &team.ID, AssigneeID: &otherAgentID, Status: string(model.ConversationStatusOpen), ChannelType: "web_widget", Channel: "web_widget", FirstReplyCreatedAt: &firstReply, WaitingSince: &waitingSince}
|
||||
teamPending := model.Conversation{AccountID: 1, TeamID: &team.ID, AssigneeID: &agentID, Status: string(model.ConversationStatusPending), ChannelType: "web_widget", Channel: "web_widget"}
|
||||
unassigned := model.Conversation{AccountID: 1, Status: string(model.ConversationStatusOpen), ChannelType: "web_widget", Channel: "web_widget"}
|
||||
otherTeamOpen := model.Conversation{AccountID: 1, TeamID: &otherTeam.ID, Status: string(model.ConversationStatusOpen), ChannelType: "web_widget", Channel: "web_widget"}
|
||||
otherAccountOpen := model.Conversation{AccountID: 2, Status: string(model.ConversationStatusOpen), ChannelType: "web_widget", Channel: "web_widget"}
|
||||
s.Require().NoError(s.db.Create(&teamOpen).Error)
|
||||
s.Require().NoError(s.db.Create(&teamReplied).Error)
|
||||
s.Require().NoError(s.db.Create(&teamWaiting).Error)
|
||||
s.Require().NoError(s.db.Create(&teamPending).Error)
|
||||
s.Require().NoError(s.db.Create(&unassigned).Error)
|
||||
s.Require().NoError(s.db.Create(&otherTeamOpen).Error)
|
||||
s.Require().NoError(s.db.Create(&otherAccountOpen).Error)
|
||||
s.Require().NoError(s.db.Create(&model.Message{AccountID: 1, ConversationID: teamReplied.ID, MessageType: string(model.MessageTypeOutgoing), Content: "already handled"}).Error)
|
||||
|
||||
metrics := httptest.NewRecorder()
|
||||
s.router.ServeHTTP(metrics, httptest.NewRequest(http.MethodGet, "/api/v2/accounts/1/live_reports/conversation_metrics", nil))
|
||||
s.Equal(http.StatusOK, metrics.Code)
|
||||
metricsBody := decodeLiveReportObject(s.T(), metrics.Body.String())
|
||||
assertLiveReportConversationMetricsShape(s.T(), metricsBody)
|
||||
s.Equal(float64(5), metricsBody["open"])
|
||||
s.Equal(float64(4), metricsBody["unattended"])
|
||||
s.Equal(float64(2), metricsBody["unassigned"])
|
||||
s.Equal(float64(1), metricsBody["pending"])
|
||||
s.NotContains(metricsBody, "success")
|
||||
|
||||
teamMetrics := httptest.NewRecorder()
|
||||
s.router.ServeHTTP(teamMetrics, httptest.NewRequest(http.MethodGet, "/api/v2/accounts/1/live_reports/conversation_metrics?team_id="+strconv.FormatUint(uint64(team.ID), 10), nil))
|
||||
s.Equal(http.StatusOK, teamMetrics.Code)
|
||||
teamMetricsBody := decodeLiveReportObject(s.T(), teamMetrics.Body.String())
|
||||
assertLiveReportConversationMetricsShape(s.T(), teamMetricsBody)
|
||||
s.Equal(float64(3), teamMetricsBody["open"])
|
||||
s.Equal(float64(2), teamMetricsBody["unattended"])
|
||||
s.Equal(float64(0), teamMetricsBody["unassigned"])
|
||||
s.Equal(float64(1), teamMetricsBody["pending"])
|
||||
|
||||
groupedByAssignee := httptest.NewRecorder()
|
||||
s.router.ServeHTTP(groupedByAssignee, httptest.NewRequest(http.MethodGet, "/api/v2/accounts/1/live_reports/grouped_conversation_metrics?group_by=assignee_id", nil))
|
||||
s.Equal(http.StatusOK, groupedByAssignee.Code)
|
||||
assigneeRows := decodeLiveReportArray(s.T(), groupedByAssignee.Body.String())
|
||||
s.Require().Len(assigneeRows, 3)
|
||||
assertLiveReportGroupedMetricShape(s.T(), assigneeRows[0], "assignee_id")
|
||||
s.Nil(assigneeRows[0]["assignee_id"])
|
||||
s.Equal(float64(2), assigneeRows[0]["open"])
|
||||
s.Equal(float64(2), assigneeRows[0]["unattended"])
|
||||
s.Equal(float64(2), assigneeRows[0]["unassigned"])
|
||||
s.Equal(float64(agentID), assigneeRows[1]["assignee_id"])
|
||||
s.Equal(float64(2), assigneeRows[1]["open"])
|
||||
s.Equal(float64(1), assigneeRows[1]["unattended"])
|
||||
s.Equal(float64(otherAgentID), assigneeRows[2]["assignee_id"])
|
||||
s.Equal(float64(1), assigneeRows[2]["open"])
|
||||
s.Equal(float64(1), assigneeRows[2]["unattended"])
|
||||
|
||||
groupedByTeam := httptest.NewRecorder()
|
||||
s.router.ServeHTTP(groupedByTeam, httptest.NewRequest(http.MethodGet, "/api/v2/accounts/1/live_reports/grouped_conversation_metrics?group_by=team_id", nil))
|
||||
s.Equal(http.StatusOK, groupedByTeam.Code)
|
||||
teamRows := decodeLiveReportArray(s.T(), groupedByTeam.Body.String())
|
||||
s.Require().Len(teamRows, 3)
|
||||
assertLiveReportGroupedMetricShape(s.T(), teamRows[0], "team_id")
|
||||
s.Nil(teamRows[0]["team_id"])
|
||||
s.Equal(float64(1), teamRows[0]["open"])
|
||||
s.Equal(float64(1), teamRows[0]["unattended"])
|
||||
s.Equal(float64(team.ID), teamRows[1]["team_id"])
|
||||
s.Equal(float64(3), teamRows[1]["open"])
|
||||
s.Equal(float64(2), teamRows[1]["unattended"])
|
||||
s.Equal(float64(otherTeam.ID), teamRows[2]["team_id"])
|
||||
s.Equal(float64(1), teamRows[2]["open"])
|
||||
|
||||
invalidGroup := httptest.NewRecorder()
|
||||
s.router.ServeHTTP(invalidGroup, httptest.NewRequest(http.MethodGet, "/api/v2/accounts/1/live_reports/grouped_conversation_metrics?group_by=bad", nil))
|
||||
s.Equal(http.StatusUnprocessableEntity, invalidGroup.Code)
|
||||
errorBody := decodeLiveReportObject(s.T(), invalidGroup.Body.String())
|
||||
s.Equal("invalid group_by", errorBody["error"])
|
||||
s.NotContains(errorBody, "success")
|
||||
}
|
||||
|
||||
func (s *LiveReportHandlerTestSuite) TestAPIV2LiveReports_StoreRefreshSequenceMatchesChatwootFrontend() {
|
||||
team := model.Team{AccountID: 1, Name: "Store Team"}
|
||||
s.Require().NoError(s.db.Create(&team).Error)
|
||||
agentID := uint(11)
|
||||
teamOpen := model.Conversation{AccountID: 1, TeamID: &team.ID, AssigneeID: &agentID, Status: string(model.ConversationStatusOpen), ChannelType: "web_widget", Channel: "web_widget"}
|
||||
unassigned := model.Conversation{AccountID: 1, Status: string(model.ConversationStatusOpen), ChannelType: "web_widget", Channel: "web_widget"}
|
||||
pending := model.Conversation{AccountID: 1, TeamID: &team.ID, AssigneeID: &agentID, Status: string(model.ConversationStatusPending), ChannelType: "web_widget", Channel: "web_widget"}
|
||||
otherAccount := model.Conversation{AccountID: 2, Status: string(model.ConversationStatusOpen), ChannelType: "web_widget", Channel: "web_widget"}
|
||||
s.Require().NoError(s.db.Create(&teamOpen).Error)
|
||||
s.Require().NoError(s.db.Create(&unassigned).Error)
|
||||
s.Require().NoError(s.db.Create(&pending).Error)
|
||||
s.Require().NoError(s.db.Create(&otherAccount).Error)
|
||||
|
||||
accountMetric := httptest.NewRecorder()
|
||||
s.router.ServeHTTP(accountMetric, httptest.NewRequest(http.MethodGet, "/api/v2/accounts/1/live_reports/conversation_metrics", nil))
|
||||
s.Equal(http.StatusOK, accountMetric.Code)
|
||||
accountPayload := decodeLiveReportObject(s.T(), accountMetric.Body.String())
|
||||
s.Equal(float64(2), accountPayload["open"])
|
||||
s.Equal(float64(2), accountPayload["unattended"])
|
||||
s.Equal(float64(1), accountPayload["unassigned"])
|
||||
s.Equal(float64(1), accountPayload["pending"])
|
||||
s.NotContains(accountPayload, "payload")
|
||||
s.NotContains(accountPayload, "success")
|
||||
|
||||
agentMetric := httptest.NewRecorder()
|
||||
s.router.ServeHTTP(agentMetric, httptest.NewRequest(http.MethodGet, "/api/v2/accounts/1/live_reports/grouped_conversation_metrics?group_by=assignee_id", nil))
|
||||
s.Equal(http.StatusOK, agentMetric.Code)
|
||||
agentRows := decodeLiveReportArray(s.T(), agentMetric.Body.String())
|
||||
s.Require().Len(agentRows, 2)
|
||||
s.Nil(agentRows[0]["assignee_id"])
|
||||
s.Equal(float64(1), agentRows[0]["open"])
|
||||
s.Equal(float64(agentID), agentRows[1]["assignee_id"])
|
||||
s.Equal(float64(1), agentRows[1]["open"])
|
||||
s.Equal(float64(1), agentRows[1]["unattended"])
|
||||
s.Equal(float64(0), agentRows[1]["unassigned"])
|
||||
|
||||
teamMetric := httptest.NewRecorder()
|
||||
s.router.ServeHTTP(teamMetric, httptest.NewRequest(http.MethodGet, "/api/v2/accounts/1/live_reports/grouped_conversation_metrics?group_by=team_id", nil))
|
||||
s.Equal(http.StatusOK, teamMetric.Code)
|
||||
teamRows := decodeLiveReportArray(s.T(), teamMetric.Body.String())
|
||||
s.Require().Len(teamRows, 2)
|
||||
s.Nil(teamRows[0]["team_id"])
|
||||
s.Equal(float64(1), teamRows[0]["open"])
|
||||
s.Equal(float64(team.ID), teamRows[1]["team_id"])
|
||||
s.Equal(float64(1), teamRows[1]["open"])
|
||||
s.Equal(float64(1), teamRows[1]["unattended"])
|
||||
s.Equal(float64(0), teamRows[1]["unassigned"])
|
||||
}
|
||||
|
||||
func (s *LiveReportHandlerTestSuite) TestGroupedConversationMetrics_InvalidGroupByReturnsChatwootError() {
|
||||
w := httptest.NewRecorder()
|
||||
req := httptest.NewRequest(http.MethodGet, "/api/v1/accounts/1/live_reports/grouped_conversation_metrics?group_by=invalid_param", nil)
|
||||
@@ -132,3 +270,39 @@ func (s *LiveReportHandlerTestSuite) TestGroupedConversationMetrics_FiltersByTea
|
||||
s.Equal(float64(1), body[0]["unattended"])
|
||||
s.Equal(float64(0), body[0]["unassigned"])
|
||||
}
|
||||
|
||||
func decodeLiveReportObject(t *testing.T, body string) map[string]interface{} {
|
||||
t.Helper()
|
||||
payload := map[string]interface{}{}
|
||||
if err := json.Unmarshal([]byte(body), &payload); err != nil {
|
||||
t.Fatalf("expected JSON object: %v\n%s", err, body)
|
||||
}
|
||||
return payload
|
||||
}
|
||||
|
||||
func decodeLiveReportArray(t *testing.T, body string) []map[string]interface{} {
|
||||
t.Helper()
|
||||
payload := []map[string]interface{}{}
|
||||
if err := json.Unmarshal([]byte(body), &payload); err != nil {
|
||||
t.Fatalf("expected JSON array: %v\n%s", err, body)
|
||||
}
|
||||
return payload
|
||||
}
|
||||
|
||||
func assertLiveReportConversationMetricsShape(t *testing.T, payload map[string]interface{}) {
|
||||
t.Helper()
|
||||
for _, key := range []string{"open", "unattended", "unassigned", "pending"} {
|
||||
if _, ok := payload[key]; !ok {
|
||||
t.Fatalf("expected live conversation metrics to include %q, got %#v", key, payload)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func assertLiveReportGroupedMetricShape(t *testing.T, payload map[string]interface{}, groupKey string) {
|
||||
t.Helper()
|
||||
for _, key := range []string{groupKey, "open", "unattended", "unassigned"} {
|
||||
if _, ok := payload[key]; !ok {
|
||||
t.Fatalf("expected live grouped metric to include %q, got %#v", key, payload)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user