package business import ( "context" "testing" "time" ) type analyticsRepoFake struct { data *AnalyticsData scope AnalyticsScope filter AnalyticsFilter } func (r *analyticsRepoFake) Aggregate(_ context.Context, _ AnalyticsKind, s AnalyticsScope, f AnalyticsFilter) (*AnalyticsData, error) { r.scope = s r.filter = f copy := *r.data copy.Rows = []*AnalyticsRow{} for _, row := range r.data.Rows { c := *row copy.Rows = append(copy.Rows, &c) } return ©, nil } func analyticsFixture() (*AnalyticsUsecase, *analyticsRepoFake) { a := &accountRepoFake{accounts: map[uint]*Account{1: {ID: 1}, 7: {ID: 7, Level: 1, IDPath: "0"}}} r := &analyticsRepoFake{data: &AnalyticsData{Available: true, Rows: []*AnalyticsRow{{AnalyticsKey: AnalyticsKey{SourceDB: "lx", ID: 1}, Name: "甲", Counts: PortraitCounts{Register: 2, Active: 4, Pay: 2, NewPay: 1}, Pay: "20.00"}, {AnalyticsKey: AnalyticsKey{SourceDB: "hw", ID: 2}, Name: "乙", Counts: PortraitCounts{Register: 3, Active: 6, Pay: 1, NewPay: 1}, Pay: "30.00"}}, GlobalCounts: &PortraitCounts{Register: 4, Active: 8, Pay: 3}, GlobalPay: "45.00"}} uc := NewAnalyticsUsecase(r, NewAccountUsecase(a), NewUserPreferenceUsecase(&channelPreferenceFake{UserPreference{IncludeSubPromote: true, IDCardDedup: true}})) uc.now = func() time.Time { return time.Date(2026, 11, 4, 9, 0, 0, 0, portraitLocation) } return uc, r } func TestGameAnalyticsSummaryUsesAllRowsAndCorrectDenominators(t *testing.T) { uc, _ := analyticsFixture() page, err := uc.List(accountContext(1, 888), GameAnalytics, AnalyticsFilter{}, 1, 1) if err != nil { t.Fatal(err) } if len(page.Rows) != 1 || page.Total != 2 || page.Rows[0].ID != 2 || page.Summary.Counts.Register != 5 || page.Summary.Pay != "50.00" || page.Summary.PayRate != "30.00" || page.Summary.ARPU != "5.00" || page.Summary.ARPPU != "16.67" { t.Fatalf("游戏汇总、分页或分母错误: %+v", page) } if page.Begin != "2026-11-04" || page.End != "2026-11-04" { t.Fatal("后台缺省统计日期应为当天") } } func TestChannelAnalyticsSummaryDependsOnViewer(t *testing.T) { uc, _ := analyticsFixture() page, err := uc.List(accountContext(7, 901), ChannelAnalytics, AnalyticsFilter{}, 1, 10) if err != nil { t.Fatal(err) } if page.Summary.Counts.Register != 5 || page.Summary.Pay != "50.00" { t.Fatal("普通商务应汇总可见展示行") } page, err = uc.List(accountContext(1, 888), ChannelAnalytics, AnalyticsFilter{}, 1, 10) if err != nil || page.Summary.Counts.Register != 4 || page.Summary.Pay != "45.00" || page.Summary.PayRate != "37.50" { t.Fatal("超管应使用筛选范围内全局去重汇总") } } func TestAnalyticsSortAndMissingData(t *testing.T) { uc, r := analyticsFixture() r.data.Rows[0].Pay = "9007199254740993.00" r.data.Rows[1].Pay = "9007199254740992.99" p, err := uc.List(accountContext(7, 901), GameAnalytics, AnalyticsFilter{SortField: "totalPay"}, 1, 10) if err != nil || p.Rows[0].ID != 1 { t.Fatal("金额排序不能经浮点数失真") } r.data.Available = false p, err = uc.List(accountContext(7, 901), ChannelAnalytics, AnalyticsFilter{}, 1, 10) if err != nil || p.Available || p.Summary != nil { t.Fatal("缺汇总表不能伪造零卡片") } begin := time.Date(2026, 11, 4, 0, 0, 0, 0, portraitLocation) end := begin.AddDate(0, 0, -1) if _, err := uc.List(accountContext(1, 888), GameAnalytics, AnalyticsFilter{StartDate: &begin, EndDate: &end}, 1, 10); err != ErrAnalyticsDateOrder { t.Fatal("倒置日期应被拒绝") } }