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

111 lines
5.6 KiB
Go

package business
import (
"context"
"testing"
"time"
)
type rankingRepoFake struct {
data *RankingData
filter RankingRange
calls int
}
func (r *rankingRepoFake) Collect(_ context.Context, _ RankingKind, _ AnalyticsScope, f RankingRange) (*RankingData, error) {
r.filter = f
r.calls++
out := &RankingData{Available: r.data.Available, Businesses: r.data.Businesses, Current: map[AnalyticsKey]*RankingMetrics{}, Previous: map[AnalyticsKey]*RankingMetrics{}}
for k, v := range r.data.Current {
c := *v
out.Current[k] = &c
}
for k, v := range r.data.Previous {
c := *v
out.Previous[k] = &c
}
return out, nil
}
func TestRankingComparisonsAndBusinessCountSemantics(t *testing.T) {
k := AnalyticsKey{SourceDB: "lx", ID: 1}
zero := AnalyticsKey{SourceDB: "lx", ID: 2}
r := &rankingRepoFake{data: &RankingData{Available: true, Current: map[AnalyticsKey]*RankingMetrics{k: {Counts: PortraitCounts{Register: 3, Active: 4, Pay: 2}, Pay: "20.005"}, zero: {Counts: PortraitCounts{Active: 1}}}, Previous: map[AnalyticsKey]*RankingMetrics{k: {Counts: PortraitCounts{Register: 2, Active: 2, Pay: 1}, Pay: "10"}}}}
a := &accountRepoFake{accounts: map[uint]*Account{7: {ID: 7, Level: 1, IDPath: "0"}, 9: {ID: 9, Level: 3, IDPath: "0,7"}}}
uc := NewRankingUsecase(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) }
begin, end := time.Date(2026, 10, 29, 0, 0, 0, 0, portraitLocation), time.Date(2026, 11, 4, 0, 0, 0, 0, portraitLocation)
p, err := uc.List(accountContext(7, 901), GameRanking, AnalyticsFilter{StartDate: &begin, EndDate: &end})
if err != nil || len(p.Register) != 1 || p.Register[0].ObjectID != "lx|1" || p.Register[0].Name != "游戏1" || *p.Register[0].MoM != "50.00" || p.Recharge[0].Value != "20.01" || *p.Recharge[0].MoM != "100.05" || len(p.PayRate) != 2 || p.PayRate[1].Value != "0.00" || p.PayRate[1].MoM != nil {
t.Fatalf("排行筛选、命名或环比错误: %+v %v", p, err)
}
if *p.PayRate[0].MoM != "0.00" || *p.PayRate[0].MoMStatus != 0 || r.filter.PreviousBegin != "2026-10-22" || r.filter.PreviousEnd != "2026-10-28" {
t.Fatal("等长上期或付费率环比错误")
}
r.data.Businesses = []*RankingBusiness{{ID: 8, Name: "二级商务", Level: 2, Channels: []AnalyticsKey{k, zero}}}
p, err = uc.List(accountContext(7, 901), BusinessRanking, AnalyticsFilter{})
if err != nil || p.Active[0].Value != "5" || p.PayRate[0].Value != "40.00" || p.Register[0].Name != "二级商务 (L2)" || p.Register[0].SourceDB != "" {
t.Fatal("商务需加总各渠道人数,不全局去重")
}
calls := r.calls
p, err = uc.List(accountContext(9, 903), BusinessRanking, AnalyticsFilter{})
if err != nil || len(p.Register) != 0 || r.calls != calls {
t.Fatal("三级商务不得展开商务榜查询")
}
r.data.Available = false
p, err = uc.List(accountContext(7, 901), ChannelRanking, AnalyticsFilter{})
if err != nil || p.Available || len(p.Active) != 0 {
t.Fatal("缺数据不能产生伪造排行")
}
}
func TestRankingTopTwentyAndStatusThreshold(t *testing.T) {
current, previous := map[AnalyticsKey]*RankingMetrics{}, map[AnalyticsKey]*RankingMetrics{}
for i := int64(1); i <= 25; i++ {
k := AnalyticsKey{SourceDB: "lx", ID: i}
current[k] = &RankingMetrics{Counts: PortraitCounts{Active: i}, Pay: "9007199254740992.99"}
previous[k] = &RankingMetrics{Counts: PortraitCounts{Active: i}}
}
current[AnalyticsKey{SourceDB: "lx", ID: 25}].Pay = "9007199254740993.00"
rows, err := buildRankingItems(ChannelRanking, "pay", current, previous)
if err != nil || len(rows) != 20 || rows[0].ID != 25 || rows[19].Rank != 20 {
t.Fatal("TOP20 或精确金额排序错误")
}
rows, err = buildRankingItems(ChannelRanking, "active", current, previous)
if err != nil || *rows[0].MoMStatus != 0 || *rows[0].MoM != "0.00" {
t.Fatal("持平环比错误")
}
}
func TestRankingPayRateRelativeGrowth(t *testing.T) {
for _, tc := range []struct {
name string
current PortraitCounts
previous *RankingMetrics
want string
status int
}{
{"increase", PortraitCounts{Pay: 12, Active: 100}, &RankingMetrics{Counts: PortraitCounts{Pay: 10, Active: 100}}, "20.00", 1},
{"decrease", PortraitCounts{Pay: 10, Active: 100}, &RankingMetrics{Counts: PortraitCounts{Pay: 20, Active: 100}}, "-50.00", -1},
{"unchanged", PortraitCounts{Pay: 10, Active: 100}, &RankingMetrics{Counts: PortraitCounts{Pay: 10, Active: 100}}, "0.00", 0},
{"no current payers", PortraitCounts{Active: 100}, &RankingMetrics{Counts: PortraitCounts{Pay: 10, Active: 100}}, "-100.00", -1},
{"zero prior rate", PortraitCounts{Pay: 10, Active: 100}, &RankingMetrics{Counts: PortraitCounts{Active: 100}}, "", 0},
{"no prior active players", PortraitCounts{Pay: 10, Active: 100}, &RankingMetrics{Counts: PortraitCounts{Pay: 1}}, "", 0},
{"no prior row", PortraitCounts{Pay: 10, Active: 100}, nil, "", 0},
} {
t.Run(tc.name, func(t *testing.T) {
key := AnalyticsKey{SourceDB: "lx", ID: 1}
rows, err := buildRankingItems(ChannelRanking, "rate", map[AnalyticsKey]*RankingMetrics{key: {Counts: tc.current}}, map[AnalyticsKey]*RankingMetrics{key: tc.previous})
if err != nil || len(rows) != 1 {
t.Fatalf("ranking: %v, %v", rows, err)
}
row := rows[0]
if tc.want == "" {
if row.MoM != nil || row.MoMStatus != nil {
t.Fatal("missing or zero baseline must not produce a growth percentage")
}
} else if row.MoM == nil || *row.MoM != tc.want || row.MoMStatus == nil || *row.MoMStatus != tc.status {
t.Fatalf("expected %s%% with status %d, got %+v", tc.want, tc.status, row)
}
})
}
}