Files
gochat/internal/service/reporting_rollup_timezone_test.go
T

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)
}
}