221 lines
11 KiB
Go
221 lines
11 KiB
Go
package service
|
|
|
|
import (
|
|
"context"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/gochat/gochat/internal/model"
|
|
"github.com/gochat/gochat/internal/repository"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
"gorm.io/driver/sqlite"
|
|
"gorm.io/gorm"
|
|
)
|
|
|
|
func setupReportingRollupTZTest(t *testing.T) (*gorm.DB, *ReportingRollupService, *ReportingBackfillService) {
|
|
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.Inbox{},
|
|
&model.Contact{},
|
|
&model.Conversation{},
|
|
&model.ReportingEvent{},
|
|
&model.ReportingEventsRollup{},
|
|
))
|
|
t.Cleanup(func() {
|
|
sqlDB, _ := db.DB()
|
|
sqlDB.Close()
|
|
})
|
|
eventRepo := repository.NewReportingEventRepo(db)
|
|
rollupRepo := repository.NewReportingEventsRollupRepo(db)
|
|
return db, NewReportingRollupService(eventRepo, rollupRepo), NewReportingBackfillService(eventRepo, rollupRepo)
|
|
}
|
|
|
|
func createReportingAccount(t *testing.T, db *gorm.DB, reportingTimezone string) *model.Account {
|
|
t.Helper()
|
|
account := &model.Account{Name: "Reports", ReportingTimezone: reportingTimezone}
|
|
require.NoError(t, db.Create(account).Error)
|
|
return account
|
|
}
|
|
|
|
func createReportingActorRows(t *testing.T, db *gorm.DB, accountID uint) (*model.User, *model.Inbox, *model.Conversation) {
|
|
t.Helper()
|
|
user := &model.User{AccountID: accountID, Name: "Agent", Email: "agent@example.com", Password: "secret", Active: true}
|
|
require.NoError(t, db.Create(user).Error)
|
|
inbox := &model.Inbox{AccountID: accountID, Name: "Web", ChannelType: "web_widget", ChannelID: 1, Enabled: true}
|
|
require.NoError(t, db.Create(inbox).Error)
|
|
contact := &model.Contact{AccountID: accountID, Name: "Customer"}
|
|
require.NoError(t, db.Create(contact).Error)
|
|
conversation := &model.Conversation{AccountID: accountID, InboxID: inbox.ID, ContactID: contact.ID, AssigneeID: &user.ID, ChannelType: inbox.ChannelType, Channel: inbox.ChannelType}
|
|
require.NoError(t, db.Create(conversation).Error)
|
|
return user, inbox, conversation
|
|
}
|
|
|
|
func createReportingEvent(t *testing.T, db *gorm.DB, accountID uint, name string, createdAt time.Time, userID, inboxID, conversationID *uint) {
|
|
t.Helper()
|
|
require.NoError(t, db.Create(&model.ReportingEvent{
|
|
Base: model.Base{CreatedAt: createdAt},
|
|
AccountID: accountID,
|
|
Name: name,
|
|
Value: 100,
|
|
ValueInBusinessHours: 50,
|
|
UserID: userID,
|
|
InboxID: inboxID,
|
|
ConversationID: conversationID,
|
|
EventStartTime: createdAt.AddDate(0, 0, -3),
|
|
EventEndTime: createdAt.AddDate(0, 0, -3).Add(time.Minute),
|
|
}).Error)
|
|
}
|
|
|
|
func TestReportingRollupSkipsBlankAndInvalidReportingTimezone(t *testing.T) {
|
|
db, rollups, _ := setupReportingRollupTZTest(t)
|
|
blank := createReportingAccount(t, db, "")
|
|
invalid := createReportingAccount(t, db, "Invalid/Timezone")
|
|
|
|
createReportingEvent(t, db, blank.ID, model.MetricNameFirstResponse, time.Date(2026, 2, 11, 15, 0, 0, 0, time.UTC), nil, nil, nil)
|
|
createReportingEvent(t, db, invalid.ID, model.MetricNameFirstResponse, time.Date(2026, 2, 11, 15, 0, 0, 0, time.UTC), nil, nil, nil)
|
|
|
|
require.NoError(t, rollups.ComputeDailyRollup(context.Background(), blank.ID, time.Date(2026, 2, 11, 0, 0, 0, 0, time.UTC)))
|
|
require.NoError(t, rollups.ComputeDailyRollup(context.Background(), invalid.ID, time.Date(2026, 2, 11, 0, 0, 0, 0, time.UTC)))
|
|
|
|
var count int64
|
|
require.NoError(t, db.Model(&model.ReportingEventsRollup{}).Count(&count).Error)
|
|
assert.Equal(t, int64(0), count)
|
|
}
|
|
|
|
func TestReportingRollupUsesReportingTimezoneCreatedAtBoundaries(t *testing.T) {
|
|
db, rollups, _ := setupReportingRollupTZTest(t)
|
|
account := createReportingAccount(t, db, "America/New_York")
|
|
user, inbox, conversation := createReportingActorRows(t, db, account.ID)
|
|
createdAt := time.Date(2026, 2, 12, 4, 0, 0, 0, time.UTC)
|
|
createReportingEvent(t, db, account.ID, "conversation_resolved", createdAt, &user.ID, &inbox.ID, &conversation.ID)
|
|
|
|
require.NoError(t, rollups.ComputeDailyRollup(context.Background(), account.ID, time.Date(2026, 2, 11, 0, 0, 0, 0, time.UTC)))
|
|
|
|
rollupDate := time.Date(2026, 2, 11, 0, 0, 0, 0, time.UTC)
|
|
var resolutionTime model.ReportingEventsRollup
|
|
require.NoError(t, db.Where("account_id = ? AND date = ? AND dimension_type = ? AND dimension_id = ? AND metric = ?", account.ID, rollupDate, model.DimensionAccount, account.ID, model.MetricResolutionTime).First(&resolutionTime).Error)
|
|
assert.Equal(t, int64(1), resolutionTime.Count)
|
|
assert.Equal(t, 100.0, resolutionTime.SumValue)
|
|
|
|
var resolutionsCount model.ReportingEventsRollup
|
|
require.NoError(t, db.Where("account_id = ? AND date = ? AND dimension_type = ? AND dimension_id = ? AND metric = ?", account.ID, rollupDate, model.DimensionAccount, account.ID, model.MetricResolutionsCount).First(&resolutionsCount).Error)
|
|
assert.Equal(t, int64(1), resolutionsCount.Count)
|
|
assert.Equal(t, 0.0, resolutionsCount.SumValue)
|
|
}
|
|
|
|
func TestAnalyticsRecordEventAppliesAdditiveLiveRollup(t *testing.T) {
|
|
db, _, _ := setupReportingRollupTZTest(t)
|
|
account := createReportingAccount(t, db, "America/New_York")
|
|
user, inbox, conversation := createReportingActorRows(t, db, account.ID)
|
|
rollupDate := time.Date(2026, 2, 11, 0, 0, 0, 0, time.UTC)
|
|
require.NoError(t, db.Create(&model.ReportingEventsRollup{
|
|
AccountID: account.ID,
|
|
Date: rollupDate,
|
|
DimensionType: model.DimensionAccount,
|
|
DimensionID: account.ID,
|
|
Metric: model.MetricResolutionTime,
|
|
Count: 7,
|
|
SumValue: 700,
|
|
SumValueBusinessHours: 350,
|
|
}).Error)
|
|
|
|
svc := NewAnalyticsService(repository.NewReportingEventRepo(db), repository.NewReportingEventsRollupRepo(db))
|
|
createdAt := time.Date(2026, 2, 12, 4, 0, 0, 0, time.UTC)
|
|
event := &model.ReportingEvent{
|
|
Base: model.Base{CreatedAt: createdAt},
|
|
AccountID: account.ID,
|
|
Name: "conversation_resolved",
|
|
Value: 100,
|
|
ValueInBusinessHours: 50,
|
|
UserID: &user.ID,
|
|
InboxID: &inbox.ID,
|
|
ConversationID: &conversation.ID,
|
|
EventStartTime: createdAt.Add(-time.Hour),
|
|
EventEndTime: createdAt,
|
|
}
|
|
require.NoError(t, svc.RecordEvent(context.Background(), event))
|
|
|
|
var rawCount int64
|
|
require.NoError(t, db.Model(&model.ReportingEvent{}).Where("account_id = ? AND name = ?", account.ID, "conversation_resolved").Count(&rawCount).Error)
|
|
assert.Equal(t, int64(1), rawCount)
|
|
|
|
var resolutionTime model.ReportingEventsRollup
|
|
require.NoError(t, db.Where("account_id = ? AND date = ? AND dimension_type = ? AND dimension_id = ? AND metric = ?", account.ID, rollupDate, model.DimensionAccount, account.ID, model.MetricResolutionTime).First(&resolutionTime).Error)
|
|
assert.Equal(t, int64(8), resolutionTime.Count)
|
|
assert.Equal(t, 800.0, resolutionTime.SumValue)
|
|
assert.Equal(t, 400.0, resolutionTime.SumValueBusinessHours)
|
|
|
|
var resolutionsCount model.ReportingEventsRollup
|
|
require.NoError(t, db.Where("account_id = ? AND date = ? AND dimension_type = ? AND dimension_id = ? AND metric = ?", account.ID, rollupDate, model.DimensionAccount, account.ID, model.MetricResolutionsCount).First(&resolutionsCount).Error)
|
|
assert.Equal(t, int64(1), resolutionsCount.Count)
|
|
assert.Equal(t, 0.0, resolutionsCount.SumValue)
|
|
|
|
var agentRows int64
|
|
require.NoError(t, db.Model(&model.ReportingEventsRollup{}).Where("account_id = ? AND date = ? AND dimension_type = ? AND dimension_id = ?", account.ID, rollupDate, model.DimensionAgent, user.ID).Count(&agentRows).Error)
|
|
assert.Equal(t, int64(2), agentRows)
|
|
}
|
|
|
|
func TestReportingRollupIgnoresUnknownEventMetrics(t *testing.T) {
|
|
db, rollups, _ := setupReportingRollupTZTest(t)
|
|
account := createReportingAccount(t, db, "America/New_York")
|
|
createReportingEvent(t, db, account.ID, "conversation_opened", time.Date(2026, 2, 11, 15, 0, 0, 0, time.UTC), nil, nil, nil)
|
|
|
|
require.NoError(t, rollups.ComputeDailyRollup(context.Background(), account.ID, time.Date(2026, 2, 11, 0, 0, 0, 0, time.UTC)))
|
|
|
|
var count int64
|
|
require.NoError(t, db.Model(&model.ReportingEventsRollup{}).Where("account_id = ?", account.ID).Count(&count).Error)
|
|
assert.Equal(t, int64(0), count)
|
|
}
|
|
|
|
func TestReportingRollupSkipsNilAgentAndInboxDimensions(t *testing.T) {
|
|
db, rollups, _ := setupReportingRollupTZTest(t)
|
|
account := createReportingAccount(t, db, "America/New_York")
|
|
createReportingEvent(t, db, account.ID, model.MetricNameFirstResponse, time.Date(2026, 2, 11, 15, 0, 0, 0, time.UTC), nil, nil, nil)
|
|
|
|
require.NoError(t, rollups.ComputeDailyRollup(context.Background(), account.ID, time.Date(2026, 2, 11, 0, 0, 0, 0, time.UTC)))
|
|
|
|
var accountRows int64
|
|
require.NoError(t, db.Model(&model.ReportingEventsRollup{}).Where("account_id = ? AND dimension_type = ?", account.ID, model.DimensionAccount).Count(&accountRows).Error)
|
|
assert.Equal(t, int64(1), accountRows)
|
|
|
|
var nilDimensionFallbacks int64
|
|
require.NoError(t, db.Model(&model.ReportingEventsRollup{}).Where("account_id = ? AND dimension_type IN ?", account.ID, []model.DimensionType{model.DimensionAgent, model.DimensionInbox}).Count(&nilDimensionFallbacks).Error)
|
|
assert.Equal(t, int64(0), nilDimensionFallbacks)
|
|
}
|
|
|
|
func TestReportingBackfillUsesReportingTimezoneAndDistinctBotHandoffs(t *testing.T) {
|
|
db, _, backfill := setupReportingRollupTZTest(t)
|
|
account := createReportingAccount(t, db, "America/New_York")
|
|
user, inbox, conversationA := createReportingActorRows(t, db, account.ID)
|
|
conversationB := &model.Conversation{AccountID: account.ID, InboxID: inbox.ID, ContactID: conversationA.ContactID, AssigneeID: &user.ID, ChannelType: inbox.ChannelType, Channel: inbox.ChannelType}
|
|
require.NoError(t, db.Create(conversationB).Error)
|
|
|
|
createReportingEvent(t, db, account.ID, "conversation_bot_handoff", time.Date(2026, 2, 11, 4, 59, 0, 0, time.UTC), &user.ID, &inbox.ID, &conversationA.ID)
|
|
createReportingEvent(t, db, account.ID, "conversation_bot_handoff", time.Date(2026, 2, 11, 5, 0, 0, 0, time.UTC), &user.ID, &inbox.ID, &conversationA.ID)
|
|
createReportingEvent(t, db, account.ID, "conversation_bot_handoff", time.Date(2026, 2, 11, 6, 0, 0, 0, time.UTC), &user.ID, &inbox.ID, &conversationA.ID)
|
|
createReportingEvent(t, db, account.ID, "conversation_bot_handoff", time.Date(2026, 2, 12, 4, 59, 0, 0, time.UTC), &user.ID, &inbox.ID, &conversationB.ID)
|
|
createReportingEvent(t, db, account.ID, "conversation_bot_handoff", time.Date(2026, 2, 12, 5, 0, 0, 0, time.UTC), &user.ID, &inbox.ID, &conversationB.ID)
|
|
|
|
require.NoError(t, backfill.BackfillDate(context.Background(), account.ID, time.Date(2026, 2, 11, 0, 0, 0, 0, time.UTC)))
|
|
|
|
rollupDate := time.Date(2026, 2, 11, 0, 0, 0, 0, time.UTC)
|
|
for _, dimension := range []struct {
|
|
typ model.DimensionType
|
|
id uint
|
|
}{
|
|
{model.DimensionAccount, account.ID},
|
|
{model.DimensionAgent, user.ID},
|
|
{model.DimensionInbox, inbox.ID},
|
|
} {
|
|
var rollup model.ReportingEventsRollup
|
|
require.NoError(t, db.Where("account_id = ? AND date = ? AND dimension_type = ? AND dimension_id = ? AND metric = ?", account.ID, rollupDate, dimension.typ, dimension.id, model.MetricBotHandoffsCount).First(&rollup).Error)
|
|
assert.Equal(t, int64(2), rollup.Count)
|
|
assert.Equal(t, 0.0, rollup.SumValue)
|
|
}
|
|
}
|