Files
kra-new/internal/biz/business/analytics_test.go
T

218 lines
9.2 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package business
import (
"context"
"testing"
"time"
)
func TestGameIDSortDoesNotFallBackToRechargeAmount(t *testing.T) {
rows := []*AnalyticsRow{{AnalyticsKey: AnalyticsKey{ID: 20, SourceDB: "lx"}, Pay: "1.00"}, {AnalyticsKey: AnalyticsKey{ID: 3, SourceDB: "lx"}, Pay: "900.00"}}
if err := sortAnalytics(rows, GameAnalytics, "gameId", true); err != nil || rows[0].ID != 3 {
t.Fatal("game ID ascending sort failed")
}
if err := sortAnalytics(rows, GameAnalytics, "gameId", false); err != nil || rows[0].ID != 20 {
t.Fatal("game ID descending sort fell back to recharge amount")
}
}
type analyticsRepoFake struct {
data *AnalyticsData
scope AnalyticsScope
filter AnalyticsFilter
fillCalls [][]AnalyticsKey
fill func([]*AnalyticsRow) error
}
func (r *analyticsRepoFake) FillNames(_ context.Context, _ AnalyticsKind, _ AnalyticsScope, _ AnalyticsFilter, rows []*AnalyticsRow) error {
keys := make([]AnalyticsKey, len(rows))
for i, row := range rows {
keys[i] = row.AnalyticsKey
}
r.fillCalls = append(r.fillCalls, keys)
if r.fill != nil {
return r.fill(rows)
}
return nil
}
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 &copy, 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}}), nil)
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 TestChannelAnalyticsAllPagesSummaryAndDefaultDateRange(t *testing.T) {
uc, repo := analyticsFixture()
repo.data.EffectiveCount = 3
for _, pageNum := range []int{1, 2, 8} {
page, err := uc.List(accountContext(7, 901), ChannelAnalytics, AnalyticsFilter{}, pageNum, 1)
if err != nil || page.Total != 2 || page.Summary.Counts.Register != 5 || page.Summary.Counts.Active != 10 || page.Summary.Counts.Pay != 3 || page.Summary.Counts.NewPay != 2 || page.Summary.Pay != "50.00" || page.Summary.PayRate != "30.00" || page.EffectiveCount != 3 {
t.Fatalf("翻页不能改变完整合计: page=%d %+v %v", pageNum, page, err)
}
if page.Begin != "" || page.End != "2026-11-04" || repo.filter.StartDate != nil {
t.Fatalf("渠道统计开始留空不应截断历史数据: %+v", repo.filter)
}
}
for _, sample := range []struct {
field string
asc bool
source string
}{{"totalPay", true, "lx"}, {"totalPay", false, "hw"}, {"", false, "hw"}} {
page, err := uc.List(accountContext(7, 901), ChannelAnalytics, AnalyticsFilter{SortField: sample.field, Ascending: sample.asc}, 1, 1)
if err != nil || page.Rows[0].SourceDB != sample.source {
t.Fatalf("取消排序应恢复稳定原始顺序: %+v %v", page, err)
}
}
}
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("倒置日期应被拒绝")
}
}
func TestGameAnalyticsCancelSortRestoresDefaultOrder(t *testing.T) {
uc, repo := analyticsFixture()
repo.data.Rows = []*AnalyticsRow{
{AnalyticsKey: AnalyticsKey{SourceDB: "lx", ID: 2}, Pay: "5"},
{AnalyticsKey: AnalyticsKey{SourceDB: "lx", ID: 1}, Pay: "10"},
{AnalyticsKey: AnalyticsKey{SourceDB: "lx", ID: 3}, Pay: "20"},
}
for _, sample := range []struct {
field string
ascending bool
first int64
}{{"totalPay", true, 2}, {"totalPay", false, 3}, {"", false, 1}} {
page, err := uc.List(accountContext(7, 901), GameAnalytics, AnalyticsFilter{SortField: sample.field, Ascending: sample.ascending}, 1, 1)
if err != nil || page.Rows[0].ID != sample.first || page.Summary.Pay != "35.00" {
t.Fatalf("切换排序不应改变汇总,取消时恢复默认顺序: %+v %v", page, err)
}
}
}
func TestAnalyticsLoadsMetadataOnlyForSelectedPage(t *testing.T) {
for _, kind := range []AnalyticsKind{ChannelAnalytics, GameAnalytics} {
t.Run(string(kind), func(t *testing.T) {
uc, repo := analyticsFixture()
for _, row := range repo.data.Rows {
row.Name = ""
}
repo.fill = func(rows []*AnalyticsRow) error {
for _, row := range rows {
row.Name = "本页资料"
row.BindingAccount = "本页商务"
}
return nil
}
page, err := uc.List(accountContext(7, 901), kind, AnalyticsFilter{SortField: "totalPay"}, 1, 1)
if err != nil {
t.Fatal(err)
}
if len(repo.fillCalls) != 1 || len(repo.fillCalls[0]) != 1 || repo.fillCalls[0][0].ID != 2 {
t.Fatalf("应只读取排序后当前页的资料: %+v", repo.fillCalls)
}
if page.Total != 2 || page.Summary.Pay != "50.00" || page.Rows[0].Name != "本页资料" || page.Rows[0].BindingAccount != "本页商务" {
t.Fatalf("延迟读取影响汇总或展示资料: %+v", page)
}
repo.fillCalls = nil
page, err = uc.List(accountContext(7, 901), kind, AnalyticsFilter{}, 3, 1)
if err != nil || len(page.Rows) != 0 || len(repo.fillCalls) != 0 || page.Summary.Pay != "50.00" {
t.Fatal("空页不应读取资料,汇总仍须保留")
}
repo.fill = func([]*AnalyticsRow) error { return ErrStorage }
if _, err := uc.List(accountContext(7, 901), kind, AnalyticsFilter{}, 1, 1); err != ErrStorage {
t.Fatalf("资料读取错误必须向上传递: %v", err)
}
})
}
}
func TestAnalyticsLoadsMetadataBeforeNameOrTypeSort(t *testing.T) {
for _, field := range []string{"promoteName", "promoteType"} {
t.Run(field, func(t *testing.T) {
uc, repo := analyticsFixture()
for _, row := range repo.data.Rows {
row.Name = ""
}
repo.fill = func(rows []*AnalyticsRow) error {
for _, row := range rows {
name, kind := "Zulu", 9
if row.ID == 2 {
name, kind = "Alpha", 1
}
row.Name, row.PromoteType = name, &kind
}
return nil
}
page, err := uc.List(accountContext(7, 901), ChannelAnalytics, AnalyticsFilter{SortField: field, Ascending: true}, 1, 1)
if err != nil || len(page.Rows) != 1 || page.Rows[0].ID != 2 || len(repo.fillCalls) != 1 || len(repo.fillCalls[0]) != 2 {
t.Fatalf("按资料字段排序前必须读取全体资料: page=%+v calls=%+v err=%v", page, repo.fillCalls, err)
}
})
}
}
func TestAnalyticsDeferredNamesPreserveOfficialAndUnknownLabels(t *testing.T) {
uc, repo := analyticsFixture()
repo.data.Rows[0].ID, repo.data.Rows[0].Name = 0, ""
repo.data.Rows[1].Name = ""
page, err := uc.List(accountContext(1, 888), ChannelAnalytics, AnalyticsFilter{SortField: "promoteId", Ascending: true}, 1, 10)
if err != nil || page.Rows[0].Name != "官方渠道" || page.Rows[1].Name != "未知渠道(ID:2)" {
t.Fatalf("默认渠道名丢失: %+v %v", page, err)
}
}