Files
gochat/internal/service/analytics_p513_test.go
T

193 lines
10 KiB
Go

package service
import (
"context"
"testing"
"time"
"github.com/gochat/gochat/internal/model"
"github.com/gochat/gochat/internal/repository"
"github.com/gochat/gochat/internal/worker"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"gorm.io/driver/sqlite"
"gorm.io/gorm"
)
func setupAnalyticsP513Test(t *testing.T) (*gorm.DB, *AnalyticsService, *model.Account, *model.Inbox, *model.Contact, *model.User, *model.Team) {
t.Helper()
db, err := gorm.Open(sqlite.Open("file:"+t.Name()+"?mode=memory&cache=private"), &gorm.Config{})
require.NoError(t, err)
require.NoError(t, db.AutoMigrate(
&model.Account{},
&model.User{},
&model.AccountUser{},
&model.Team{},
&model.Inbox{},
&model.Contact{},
&model.Conversation{},
&model.Message{},
&model.ReportingEvent{},
&model.ReportingEventsRollup{},
&model.AgentBot{},
&model.AgentBotInbox{},
&model.Tag{},
&model.ConversationLabel{},
&model.BackgroundJob{},
))
t.Cleanup(func() {
sqlDB, _ := db.DB()
sqlDB.Close()
})
account := &model.Account{Name: "Analytics"}
require.NoError(t, db.Create(account).Error)
user := &model.User{AccountID: account.ID, Name: "Agent", Email: "agent@example.com", Password: "secret", Active: true}
require.NoError(t, db.Create(user).Error)
require.NoError(t, db.Create(&model.AccountUser{AccountID: account.ID, UserID: user.ID, Role: string(model.AccountUserRoleAgent)}).Error)
team := &model.Team{AccountID: account.ID, Name: "Support"}
require.NoError(t, db.Create(team).Error)
inbox := &model.Inbox{AccountID: account.ID, Name: "Web", ChannelType: "web_widget", ChannelID: 1, Enabled: true}
require.NoError(t, db.Create(inbox).Error)
contact := &model.Contact{AccountID: account.ID, Name: "Customer", Email: "customer@example.com"}
require.NoError(t, db.Create(contact).Error)
svc := NewAnalyticsService(repository.NewReportingEventRepo(db), repository.NewReportingEventsRollupRepo(db))
return db, svc, account, inbox, contact, user, team
}
func TestAnalyticsLiveConversationMetricsAreDerivedFromConversations(t *testing.T) {
db, svc, account, inbox, contact, user, team := setupAnalyticsP513Test(t)
now := int64(1760000000)
firstReply := now - 60
assigned := &model.Conversation{AccountID: account.ID, InboxID: inbox.ID, ContactID: contact.ID, AssigneeID: &user.ID, TeamID: &team.ID, Status: string(model.ConversationStatusOpen), ChannelType: inbox.ChannelType, Channel: inbox.ChannelType}
unassigned := &model.Conversation{AccountID: account.ID, InboxID: inbox.ID, ContactID: contact.ID, TeamID: &team.ID, Status: string(model.ConversationStatusOpen), ChannelType: inbox.ChannelType, Channel: inbox.ChannelType, FirstReplyCreatedAt: &firstReply}
pending := &model.Conversation{AccountID: account.ID, InboxID: inbox.ID, ContactID: contact.ID, Status: string(model.ConversationStatusPending), ChannelType: inbox.ChannelType, Channel: inbox.ChannelType}
otherAccount := &model.Account{Name: "Other"}
require.NoError(t, db.Create(otherAccount).Error)
other := &model.Conversation{AccountID: otherAccount.ID, InboxID: inbox.ID, ContactID: contact.ID, Status: string(model.ConversationStatusOpen), ChannelType: inbox.ChannelType, Channel: inbox.ChannelType}
require.NoError(t, db.Create(assigned).Error)
require.NoError(t, db.Create(unassigned).Error)
require.NoError(t, db.Create(pending).Error)
require.NoError(t, db.Create(other).Error)
metrics, err := svc.GetConversationMetrics(context.Background(), account.ID)
require.NoError(t, err)
assert.Equal(t, int64(2), metrics.OpenCount)
assert.Equal(t, int64(1), metrics.UnattendedCount)
assert.Equal(t, int64(1), metrics.UnassignedCount)
assert.Equal(t, int64(1), metrics.PendingCount)
teamMetrics, err := svc.GetConversationMetricsForTeam(context.Background(), account.ID, team.ID)
require.NoError(t, err)
assert.Equal(t, int64(2), teamMetrics.OpenCount)
assert.Equal(t, int64(0), teamMetrics.PendingCount)
grouped, err := svc.GetGroupedConversationMetrics(context.Background(), account.ID, "assignee_id")
require.NoError(t, err)
require.Len(t, grouped, 2)
assert.Equal(t, int64(1), grouped[0]["open"])
assert.Nil(t, grouped[0]["assignee_id"])
assert.Equal(t, user.ID, grouped[1]["assignee_id"])
assert.Equal(t, int64(1), grouped[1]["open"])
}
func TestAnalyticsReportsUsePersistedConversationMessageAndEventRows(t *testing.T) {
db, svc, account, inbox, contact, user, team := setupAnalyticsP513Test(t)
since := time.Date(2026, 6, 1, 0, 0, 0, 0, time.UTC)
until := since.Add(24 * time.Hour)
resolvedAt := since.Add(3 * time.Hour)
conv := &model.Conversation{AccountID: account.ID, InboxID: inbox.ID, ContactID: contact.ID, AssigneeID: &user.ID, TeamID: &team.ID, Status: string(model.ConversationStatusResolved), ResolvedAt: &resolvedAt, ChannelType: inbox.ChannelType, Channel: inbox.ChannelType}
require.NoError(t, db.Create(conv).Error)
require.NoError(t, db.Model(conv).Updates(map[string]interface{}{"created_at": since.Add(time.Hour)}).Error)
senderID := user.ID
require.NoError(t, db.Create(&model.Message{Base: model.Base{CreatedAt: since.Add(2 * time.Hour)}, AccountID: account.ID, ConversationID: conv.ID, InboxID: inbox.ID, SenderID: &senderID, SenderType: "User", MessageType: string(model.MessageTypeOutgoing), Content: "hello"}).Error)
require.NoError(t, db.Create(&model.Message{Base: model.Base{CreatedAt: since.Add(90 * time.Minute)}, AccountID: account.ID, ConversationID: conv.ID, InboxID: inbox.ID, MessageType: string(model.MessageTypeIncoming), Content: "hi"}).Error)
bot := &model.AgentBot{AccountID: &account.ID, Name: "Bot"}
require.NoError(t, db.Create(bot).Error)
require.NoError(t, db.Create(&model.AgentBotInbox{AccountID: &account.ID, AgentBotID: bot.ID, InboxID: inbox.ID, Status: model.AgentBotInboxActive}).Error)
tag := &model.Tag{AccountID: account.ID, Name: "vip"}
require.NoError(t, db.Create(tag).Error)
require.NoError(t, db.Create(&model.ConversationLabel{AccountID: account.ID, ConversationID: conv.ID, TagID: tag.ID}).Error)
require.NoError(t, db.Create(&model.ReportingEvent{Base: model.Base{CreatedAt: since.Add(30 * time.Minute)}, AccountID: account.ID, Name: model.MetricNameFirstResponse, Value: 1800, ConversationID: &conv.ID, InboxID: &inbox.ID, UserID: &user.ID, EventStartTime: since, EventEndTime: since.Add(30 * time.Minute)}).Error)
require.NoError(t, db.Create(&model.ReportingEvent{Base: model.Base{CreatedAt: since.Add(time.Hour)}, AccountID: account.ID, Name: "conversation_bot_resolved", Value: 1, ConversationID: &conv.ID, InboxID: &inbox.ID, EventStartTime: since, EventEndTime: since.Add(time.Hour)}).Error)
summary, err := svc.GetConversationsSummary(context.Background(), account.ID, since, until)
require.NoError(t, err)
summaryMap := summary.(map[string]interface{})
assert.Equal(t, int64(1), summaryMap["conversations_count"])
assert.Equal(t, int64(1), summaryMap["incoming_messages_count"])
assert.Equal(t, int64(1), summaryMap["outgoing_messages_count"])
assert.Equal(t, int64(1), summaryMap["resolutions_count"])
assert.Equal(t, 1800.0, summaryMap["avg_first_response_time"])
botSummary, err := svc.GetBotSummary(context.Background(), account.ID, since, until)
require.NoError(t, err)
assert.Equal(t, int64(1), botSummary.BotResolutionsCount)
assert.Equal(t, int64(0), botSummary.BotHandoffsCount)
assert.NotNil(t, botSummary.Previous)
botMetrics, err := svc.GetBotMetrics(context.Background(), account.ID, since, until)
require.NoError(t, err)
botMap := botMetrics.(map[string]interface{})
assert.Equal(t, int64(1), botMap["conversation_count"])
assert.Equal(t, int64(1), botMap["message_count"])
assert.Equal(t, 100, botMap["resolution_rate"])
distribution, err := svc.GetFirstResponseTimeDistribution(context.Background(), account.ID, since, until)
require.NoError(t, err)
distMap := distribution.(map[string]map[string]int64)
assert.Equal(t, int64(1), distMap["web_widget"]["0-1h"])
outgoing, err := svc.GetOutgoingMessagesCountGrouped(context.Background(), account.ID, since, until, "inbox")
require.NoError(t, err)
outRows := outgoing.([]map[string]interface{})
require.Len(t, outRows, 1)
assert.Equal(t, inbox.ID, outRows[0]["id"])
assert.Equal(t, int64(1), outRows[0]["outgoing_messages_count"])
matrix, err := svc.GetInboxLabelMatrix(context.Background(), account.ID)
require.NoError(t, err)
matrixMap := matrix.(map[string]interface{})
assert.Equal(t, [][]int64{{1}}, matrixMap["matrix"])
}
func TestAnalyticsTimeseriesAndRollupWorker(t *testing.T) {
db, svc, account, inbox, contact, user, _ := setupAnalyticsP513Test(t)
since := time.Date(2026, 6, 2, 0, 0, 0, 0, time.UTC)
until := since.Add(48 * time.Hour)
conv := &model.Conversation{AccountID: account.ID, InboxID: inbox.ID, ContactID: contact.ID, AssigneeID: &user.ID, Status: string(model.ConversationStatusOpen), ChannelType: inbox.ChannelType, Channel: inbox.ChannelType}
require.NoError(t, db.Create(conv).Error)
require.NoError(t, db.Model(conv).Updates(map[string]interface{}{"created_at": since.Add(2 * time.Hour)}).Error)
require.NoError(t, db.Create(&model.ReportingEvent{Base: model.Base{CreatedAt: since.Add(3 * time.Hour)}, AccountID: account.ID, Name: model.MetricNameFirstResponse, Value: 120, ConversationID: &conv.ID, InboxID: &inbox.ID, UserID: &user.ID, EventStartTime: since, EventEndTime: since.Add(2 * time.Minute)}).Error)
points, err := svc.GetTimeseries(context.Background(), account.ID, "conversations_count", since, until, "account", 0, "day", false)
require.NoError(t, err)
require.Len(t, points, 1)
assert.Equal(t, float64(1), points[0].Value)
assert.Equal(t, since.Unix(), points[0].Timestamp)
avgPoints, err := svc.GetTimeseries(context.Background(), account.ID, "avg_first_response_time", since, until, "agent", user.ID, "day", false)
require.NoError(t, err)
require.Len(t, avgPoints, 1)
assert.Equal(t, 120.0, avgPoints[0].Value)
assert.Equal(t, int64(1), avgPoints[0].Count)
wp := worker.NewWorkerPool(db)
svc.SetWorkerPool(wp)
job, err := EnqueueReportingRollupDay(context.Background(), wp, account.ID, since)
require.NoError(t, err)
require.NotNil(t, job)
again, err := EnqueueReportingRollupDay(context.Background(), wp, account.ID, since)
require.NoError(t, err)
assert.Equal(t, job.ID, again.ID)
processed, err := wp.ProcessOne(context.Background())
require.NoError(t, err)
assert.True(t, processed)
processed, err = wp.ProcessOne(context.Background())
require.NoError(t, err)
assert.False(t, processed)
var rollupCount int64
require.NoError(t, db.Model(&model.ReportingEventsRollup{}).Where("account_id = ? AND date = ?", account.ID, since).Count(&rollupCount).Error)
assert.Greater(t, rollupCount, int64(0))
}