Files
gochat/internal/service/analytics_service_test.go_BAK
T
2026-06-04 15:44:48 +08:00

530 lines
17 KiB
Plaintext

package service
import (
"context"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/gochat/gochat/internal/model"
)
// ========== GetSummary ==========
func TestAnalyticsService_GetSummary_空结果(t *testing.T) {
_, _, _, svc := setupAnalyticsService(t)
since := time.Date(2026, 1, 1, 0, 0, 0, 0, time.UTC)
until := time.Date(2026, 1, 31, 0, 0, 0, 0, time.UTC)
resp, err := svc.GetSummary(context.Background(), 1, since, until)
require.NoError(t, err)
assert.NotNil(t, resp)
assert.Empty(t, resp.Metrics)
}
func TestAnalyticsService_GetSummary_单metric(t *testing.T) {
db, _, _, svc := setupAnalyticsService(t)
account := createTestAccount(t, db)
createTestRollup(t, db, account.ID, func(r *model.ReportingEventsRollup) {
r.Metric = model.MetricFirstResponse
r.Count = 3
r.SumValue = 360.0
r.SumValueBusinessHours = 180.0
})
since := time.Date(2026, 1, 1, 0, 0, 0, 0, time.UTC)
until := time.Date(2026, 1, 31, 23, 59, 59, 0, time.UTC)
resp, err := svc.GetSummary(context.Background(), account.ID, since, until)
require.NoError(t, err)
require.Len(t, resp.Metrics, 1)
assert.Equal(t, "first_response", resp.Metrics[0].Metric)
assert.Equal(t, int64(3), resp.Metrics[0].Count)
assert.InDelta(t, 120.0, resp.Metrics[0].AverageValue, 0.01) // 360/3=120
assert.InDelta(t, 60.0, resp.Metrics[0].AverageBusinessHours, 0.01) // 180/3=60
}
func TestAnalyticsService_GetSummary_多metric合并(t *testing.T) {
db, _, _, svc := setupAnalyticsService(t)
account := createTestAccount(t, db)
// Create two rollups with the same metric, they should be merged
createTestRollup(t, db, account.ID, func(r *model.ReportingEventsRollup) {
r.Metric = model.MetricFirstResponse
r.Count = 2
r.SumValue = 240.0
r.SumValueBusinessHours = 120.0
r.Date = time.Date(2026, 1, 10, 0, 0, 0, 0, time.UTC)
})
createTestRollup(t, db, account.ID, func(r *model.ReportingEventsRollup) {
r.Metric = model.MetricFirstResponse
r.Count = 3
r.SumValue = 360.0
r.SumValueBusinessHours = 180.0
r.Date = time.Date(2026, 1, 15, 0, 0, 0, 0, time.UTC)
})
since := time.Date(2026, 1, 1, 0, 0, 0, 0, time.UTC)
until := time.Date(2026, 1, 31, 23, 59, 59, 0, time.UTC)
resp, err := svc.GetSummary(context.Background(), account.ID, since, until)
require.NoError(t, err)
require.Len(t, resp.Metrics, 1)
assert.Equal(t, "first_response", resp.Metrics[0].Metric)
assert.Equal(t, int64(5), resp.Metrics[0].Count) // 2+3
assert.InDelta(t, 120.0, resp.Metrics[0].AverageValue, 0.01) // (240+360)/(2+3)=120
assert.InDelta(t, 60.0, resp.Metrics[0].AverageBusinessHours, 0.01) // (120+180)/(2+3)=60
}
func TestAnalyticsService_GetSummary_不同metric类型(t *testing.T) {
db, _, _, svc := setupAnalyticsService(t)
account := createTestAccount(t, db)
createTestRollup(t, db, account.ID, func(r *model.ReportingEventsRollup) {
r.Metric = model.MetricFirstResponse
r.Count = 2
r.SumValue = 240.0
r.SumValueBusinessHours = 120.0
})
createTestRollup(t, db, account.ID, func(r *model.ReportingEventsRollup) {
r.Metric = model.MetricResolutionTime
r.Count = 4
r.SumValue = 800.0
r.SumValueBusinessHours = 400.0
})
since := time.Date(2026, 1, 1, 0, 0, 0, 0, time.UTC)
until := time.Date(2026, 1, 31, 23, 59, 59, 0, time.UTC)
resp, err := svc.GetSummary(context.Background(), account.ID, since, until)
require.NoError(t, err)
assert.Len(t, resp.Metrics, 2) // two distinct metric types
}
// ========== GetAgentMetrics ==========
func TestAnalyticsService_GetAgentMetrics_空结果(t *testing.T) {
_, _, _, svc := setupAnalyticsService(t)
since := time.Date(2026, 1, 1, 0, 0, 0, 0, time.UTC)
until := time.Date(2026, 1, 31, 0, 0, 0, 0, time.UTC)
dims, err := svc.GetAgentMetrics(context.Background(), 1, since, until)
require.NoError(t, err)
assert.Empty(t, dims)
}
func TestAnalyticsService_GetAgentMetrics_有数据(t *testing.T) {
db, _, _, svc := setupAnalyticsService(t)
account := createTestAccount(t, db)
user := createTestUser(t, db, account.ID)
createTestRollup(t, db, account.ID, func(r *model.ReportingEventsRollup) {
r.DimensionType = model.DimensionAgent
r.DimensionID = user.ID
r.Metric = model.MetricFirstResponse
r.Count = 2
r.SumValue = 200.0
r.SumValueBusinessHours = 100.0
})
since := time.Date(2026, 1, 1, 0, 0, 0, 0, time.UTC)
until := time.Date(2026, 1, 31, 23, 59, 59, 0, time.UTC)
dims, err := svc.GetAgentMetrics(context.Background(), account.ID, since, until)
require.NoError(t, err)
require.Len(t, dims, 1)
assert.Equal(t, user.ID, dims[0].DimensionID)
assert.Equal(t, "agent", dims[0].DimensionType)
require.Len(t, dims[0].Metrics, 1)
assert.Equal(t, int64(2), dims[0].Metrics[0].Count)
assert.InDelta(t, 100.0, dims[0].Metrics[0].AverageValue, 0.01) // 200/2=100
}
// ========== GetInboxMetrics ==========
func TestAnalyticsService_GetInboxMetrics_有数据(t *testing.T) {
db, _, _, svc := setupAnalyticsService(t)
account := createTestAccount(t, db)
inbox := createTestInbox(t, db, account.ID, "web_widget")
createTestRollup(t, db, account.ID, func(r *model.ReportingEventsRollup) {
r.DimensionType = model.DimensionInbox
r.DimensionID = inbox.ID
r.Metric = model.MetricReplyTime
r.Count = 3
r.SumValue = 450.0
r.SumValueBusinessHours = 225.0
})
since := time.Date(2026, 1, 1, 0, 0, 0, 0, time.UTC)
until := time.Date(2026, 1, 31, 23, 59, 59, 0, time.UTC)
dims, err := svc.GetInboxMetrics(context.Background(), account.ID, since, until)
require.NoError(t, err)
require.Len(t, dims, 1)
assert.Equal(t, inbox.ID, dims[0].DimensionID)
assert.Equal(t, "inbox", dims[0].DimensionType)
}
// ========== GetLabelMetrics ==========
func TestAnalyticsService_GetLabelMetrics_有数据(t *testing.T) {
db, _, _, svc := setupAnalyticsService(t)
account := createTestAccount(t, db)
// LabelMetrics uses DimensionAccount internally
createTestRollup(t, db, account.ID, func(r *model.ReportingEventsRollup) {
r.DimensionType = model.DimensionAccount
r.DimensionID = account.ID
r.Metric = model.MetricFirstResponse
r.Count = 1
r.SumValue = 100.0
r.SumValueBusinessHours = 50.0
})
since := time.Date(2026, 1, 1, 0, 0, 0, 0, time.UTC)
until := time.Date(2026, 1, 31, 23, 59, 59, 0, time.UTC)
dims, err := svc.GetLabelMetrics(context.Background(), account.ID, since, until)
require.NoError(t, err)
require.Len(t, dims, 1)
assert.Equal(t, account.ID, dims[0].DimensionID)
}
// ========== GetTeamMetrics ==========
func TestAnalyticsService_GetTeamMetrics_空结果(t *testing.T) {
_, _, _, svc := setupAnalyticsService(t)
since := time.Date(2026, 1, 1, 0, 0, 0, 0, time.UTC)
until := time.Date(2026, 1, 31, 0, 0, 0, 0, time.UTC)
dims, err := svc.GetTeamMetrics(context.Background(), 999, since, until)
require.NoError(t, err)
assert.Empty(t, dims)
}
func TestAnalyticsService_GetTeamMetrics_有数据(t *testing.T) {
db, _, _, svc := setupAnalyticsService(t)
account := createTestAccount(t, db)
createTestRollup(t, db, account.ID, func(r *model.ReportingEventsRollup) {
r.DimensionType = model.DimensionTeam
r.DimensionID = account.ID // team is approximated as account
r.Metric = model.MetricResolutionTime
r.Count = 10
r.SumValue = 500.0
r.SumValueBusinessHours = 250.0
})
since := time.Date(2026, 1, 1, 0, 0, 0, 0, time.UTC)
until := time.Date(2026, 1, 31, 23, 59, 59, 0, time.UTC)
dims, err := svc.GetTeamMetrics(context.Background(), account.ID, since, until)
require.NoError(t, err)
require.Len(t, dims, 1)
assert.Equal(t, "team", dims[0].DimensionType)
}
// ========== GetConversationTraffic ==========
func TestAnalyticsService_GetConversationTraffic_空结果(t *testing.T) {
_, _, _, svc := setupAnalyticsService(t)
since := time.Date(2026, 1, 1, 0, 0, 0, 0, time.UTC)
until := time.Date(2026, 1, 31, 0, 0, 0, 0, time.UTC)
points, err := svc.GetConversationTraffic(context.Background(), 1, since, until)
require.NoError(t, err)
assert.Empty(t, points)
}
func TestAnalyticsService_GetConversationTraffic_有数据(t *testing.T) {
db, _, _, svc := setupAnalyticsService(t)
account := createTestAccount(t, db)
date1 := time.Date(2026, 1, 10, 0, 0, 0, 0, time.UTC)
date2 := time.Date(2026, 1, 15, 0, 0, 0, 0, time.UTC)
createTestRollup(t, db, account.ID, func(r *model.ReportingEventsRollup) {
r.Metric = model.MetricResolutionsCount
r.Date = date1
r.Count = 5
})
createTestRollup(t, db, account.ID, func(r *model.ReportingEventsRollup) {
r.Metric = model.MetricResolutionsCount
r.Date = date2
r.Count = 8
})
since := time.Date(2026, 1, 1, 0, 0, 0, 0, time.UTC)
until := time.Date(2026, 1, 31, 23, 59, 59, 0, time.UTC)
points, err := svc.GetConversationTraffic(context.Background(), account.ID, since, until)
require.NoError(t, err)
require.Len(t, points, 2)
// Verify dates and counts appear in the output
found10 := false
found15 := false
for _, p := range points {
if p.Date.Format("2006-01-02") == "2026-01-10" {
assert.Equal(t, int64(5), p.Count)
found10 = true
}
if p.Date.Format("2006-01-02") == "2026-01-15" {
assert.Equal(t, int64(8), p.Count)
found15 = true
}
}
assert.True(t, found10, "expected traffic point for 2026-01-10")
assert.True(t, found15, "expected traffic point for 2026-01-15")
}
// ========== GetConversationMetrics ==========
func TestAnalyticsService_GetConversationMetrics_空结果(t *testing.T) {
db, _, _, svc := setupAnalyticsService(t)
account := createTestAccount(t, db)
metrics, err := svc.GetConversationMetrics(context.Background(), account.ID)
require.NoError(t, err)
assert.NotNil(t, metrics)
assert.Equal(t, int64(0), metrics.ResolvedCount)
}
func TestAnalyticsService_GetConversationMetrics_有resolved(t *testing.T) {
db, _, _, svc := setupAnalyticsService(t)
account := createTestAccount(t, db)
now := time.Now()
startOfDay := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, now.Location())
// Create resolution events for today
for i := 0; i < 3; i++ {
createTestReportingEvent(t, db, account.ID, func(e *model.ReportingEvent) {
e.Name = model.MetricNameResolutionTime
e.Value = 300.0
e.ValueInBusinessHours = 150.0
e.CreatedAt = startOfDay.Add(time.Duration(i) * time.Hour)
})
}
metrics, err := svc.GetConversationMetrics(context.Background(), account.ID)
require.NoError(t, err)
assert.NotNil(t, metrics)
assert.Equal(t, int64(3), metrics.ResolvedCount)
}
// ========== RecordEvent ==========
func TestAnalyticsService_RecordEvent_成功(t *testing.T) {
db, eventRepo, _, svc := setupAnalyticsService(t)
account := createTestAccount(t, db)
event := &model.ReportingEvent{
AccountID: account.ID,
Name: model.MetricNameFirstResponse,
Value: 120.5,
ValueInBusinessHours: 60.0,
}
err := svc.RecordEvent(context.Background(), event)
require.NoError(t, err)
assert.NotZero(t, event.ID) // ID should be assigned after creation
// Verify it's persisted
found, err := eventRepo.FindByMetric(context.Background(), account.ID, model.MetricNameFirstResponse, time.Now().Add(-24*time.Hour), time.Now().Add(24*time.Hour))
require.NoError(t, err)
require.Len(t, found, 1)
assert.Equal(t, 120.5, found[0].Value)
}
func TestAnalyticsService_RecordEvent_带可选字段(t *testing.T) {
db, _, _, svc := setupAnalyticsService(t)
account := createTestAccount(t, db)
inbox := createTestInbox(t, db, account.ID, "web_widget")
user := createTestUser(t, db, account.ID)
event := &model.ReportingEvent{
AccountID: account.ID,
InboxID: &inbox.ID,
UserID: &user.ID,
Name: model.MetricNameReplyTime,
Value: 45.0,
ValueInBusinessHours: 22.5,
}
err := svc.RecordEvent(context.Background(), event)
require.NoError(t, err)
assert.NotZero(t, event.ID)
assert.Equal(t, inbox.ID, *event.InboxID)
assert.Equal(t, user.ID, *event.UserID)
}
// ========== RollupDaily ==========
func TestAnalyticsService_RollupDaily_成功(t *testing.T) {
db, _, rollupRepo, svc := setupAnalyticsService(t)
account := createTestAccount(t, db)
date := time.Date(2026, 5, 20, 0, 0, 0, 0, time.UTC)
// Create events for that day
createTestReportingEvent(t, db, account.ID, func(e *model.ReportingEvent) {
e.Name = model.MetricNameFirstResponse
e.Value = 120.0
e.ValueInBusinessHours = 60.0
e.CreatedAt = date.Add(2 * time.Hour)
})
createTestReportingEvent(t, db, account.ID, func(e *model.ReportingEvent) {
e.Name = model.MetricNameFirstResponse
e.Value = 240.0
e.ValueInBusinessHours = 120.0
e.CreatedAt = date.Add(5 * time.Hour)
})
err := svc.RollupDaily(context.Background(), account.ID, date)
require.NoError(t, err)
// Verify rollups were created for the first_response metric
since := time.Date(2026, 5, 20, 0, 0, 0, 0, time.UTC)
until := time.Date(2026, 5, 20, 23, 59, 59, 0, time.UTC)
rollups, err := rollupRepo.FindByAccountAndDateRange(context.Background(), account.ID, since, until)
require.NoError(t, err)
assert.NotEmpty(t, rollups, "expected at least one rollup record to be created")
// Find the account-dimension rollup for first_response
var accountRollup *model.ReportingEventsRollup
for _, r := range rollups {
if r.DimensionType == model.DimensionAccount && r.Metric == model.MetricFirstResponse {
accountRollup = &r
break
}
}
require.NotNil(t, accountRollup, "expected account-level first_response rollup")
assert.Equal(t, int64(2), accountRollup.Count)
assert.InDelta(t, 360.0, accountRollup.SumValue, 0.01) // 120+240
assert.InDelta(t, 180.0, accountRollup.SumValueBusinessHours, 0.01) // 60+120
}
func TestAnalyticsService_RollupDaily_无事件(t *testing.T) {
db, _, _, svc := setupAnalyticsService(t)
account := createTestAccount(t, db)
date := time.Date(2026, 5, 20, 0, 0, 0, 0, time.UTC)
err := svc.RollupDaily(context.Background(), account.ID, date)
require.NoError(t, err) // should not error even with no events
}
func TestAnalyticsService_RollupDaily_多metric类型(t *testing.T) {
db, _, rollupRepo, svc := setupAnalyticsService(t)
account := createTestAccount(t, db)
date := time.Date(2026, 5, 20, 0, 0, 0, 0, time.UTC)
// Create events with different metric names
createTestReportingEvent(t, db, account.ID, func(e *model.ReportingEvent) {
e.Name = model.MetricNameFirstResponse
e.Value = 100.0
e.ValueInBusinessHours = 50.0
e.CreatedAt = date.Add(1 * time.Hour)
})
createTestReportingEvent(t, db, account.ID, func(e *model.ReportingEvent) {
e.Name = model.MetricNameResolutionTime
e.Value = 500.0
e.ValueInBusinessHours = 250.0
e.CreatedAt = date.Add(3 * time.Hour)
})
err := svc.RollupDaily(context.Background(), account.ID, date)
require.NoError(t, err)
since := time.Date(2026, 5, 20, 0, 0, 0, 0, time.UTC)
until := time.Date(2026, 5, 20, 23, 59, 59, 0, time.UTC)
rollups, err := rollupRepo.FindByAccountAndDateRange(context.Background(), account.ID, since, until)
require.NoError(t, err)
// We should have rollups for at least 2 different metrics across 4 dimension types
metricSet := map[model.RollupMetric]bool{}
for _, r := range rollups {
metricSet[r.Metric] = true
}
assert.True(t, metricSet[model.MetricFirstResponse], "expected first_response rollup")
assert.True(t, metricSet[model.MetricResolutionTime], "expected resolution_time rollup")
}
// ========== groupEventsByDimension (indirect via RollupDaily) ==========
func TestAnalyticsService_RollupDaily_agent维度分组(t *testing.T) {
db, _, rollupRepo, svc := setupAnalyticsService(t)
account := createTestAccount(t, db)
user := createTestUser(t, db, account.ID)
date := time.Date(2026, 5, 20, 0, 0, 0, 0, time.UTC)
// Create event with a user_id (agent dimension)
createTestReportingEvent(t, db, account.ID, func(e *model.ReportingEvent) {
e.Name = model.MetricNameFirstResponse
e.UserID = &user.ID
e.Value = 200.0
e.ValueInBusinessHours = 100.0
e.CreatedAt = date.Add(2 * time.Hour)
})
err := svc.RollupDaily(context.Background(), account.ID, date)
require.NoError(t, err)
since := time.Date(2026, 5, 20, 0, 0, 0, 0, time.UTC)
until := time.Date(2026, 5, 20, 23, 59, 59, 0, time.UTC)
rollups, err := rollupRepo.FindByAccountAndDateRange(context.Background(), account.ID, since, until)
require.NoError(t, err)
// Find the agent-dimension rollup
var agentRollup *model.ReportingEventsRollup
for _, r := range rollups {
if r.DimensionType == model.DimensionAgent && r.DimensionID == user.ID {
agentRollup = &r
break
}
}
require.NotNil(t, agentRollup, "expected agent-dimension rollup with user's ID")
assert.Equal(t, int64(1), agentRollup.Count)
}
// ========== PG-only feature tests (skipped with SQLite) ==========
// The following tests exercise features that rely on PG-specific SQL
// (ILIKE, date_trunc, NOW(), etc.). They are skipped under SQLite.
func TestAnalyticsService_GetSummary_PG日期范围查询(t *testing.T) {
skipIfSQLite(t)
// This would test date_trunc/ILIKE-based queries that only work on PG.
// Under SQLite it is automatically skipped.
db, _, _, svc := setupAnalyticsService(t)
account := createTestAccount(t, db)
since := time.Date(2026, 1, 1, 0, 0, 0, 0, time.UTC)
until := time.Date(2026, 1, 31, 23, 59, 59, 0, time.UTC)
resp, err := svc.GetSummary(context.Background(), account.ID, since, until)
require.NoError(t, err)
assert.NotNil(t, resp)
}
func TestAnalyticsService_RollupDaily_PG复杂聚合(t *testing.T) {
skipIfSQLite(t)
// This would test PG-only aggregation features.
db, _, _, svc := setupAnalyticsService(t)
account := createTestAccount(t, db)
date := time.Date(2026, 5, 20, 0, 0, 0, 0, time.UTC)
err := svc.RollupDaily(context.Background(), account.ID, date)
require.NoError(t, err)
}