143 lines
6.3 KiB
Go
143 lines
6.3 KiB
Go
package business
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"math/big"
|
|
"sort"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
type workbenchRepoFake struct {
|
|
totals *WorkbenchTotals
|
|
data *WorkbenchRankData
|
|
window WorkbenchWindow
|
|
filter AnalyticsFilter
|
|
scope AnalyticsScope
|
|
named []*WorkbenchRankRow
|
|
}
|
|
|
|
func TestWorkbenchTopTenMatchesFullStableSort(t *testing.T) {
|
|
for _, kind := range []WorkbenchRankKind{WorkbenchPayHigh, WorkbenchPayLow} {
|
|
uc, repo := workbenchFixture()
|
|
for i := 0; i < 3000; i++ {
|
|
repo.data.Rows = append(repo.data.Rows, &WorkbenchRankRow{AnalyticsKey: AnalyticsKey{ID: int64(i%11 + 1), SourceDB: []string{"lx", "hw", "yq"}[i%3]}, Name: fmt.Sprintf("candidate-%04d", i), Value: big.NewRat(int64(i%7-2), 100).FloatString(2)})
|
|
}
|
|
expected := make([]*WorkbenchRankRow, 0, len(repo.data.Rows))
|
|
for _, row := range repo.data.Rows {
|
|
n, _ := new(big.Rat).SetString(row.Value)
|
|
if kind == WorkbenchPayLow || n.Sign() > 0 {
|
|
expected = append(expected, row)
|
|
}
|
|
}
|
|
sort.SliceStable(expected, func(i, j int) bool {
|
|
a, b := expected[i], expected[j]
|
|
av, _ := new(big.Rat).SetString(a.Value)
|
|
bv, _ := new(big.Rat).SetString(b.Value)
|
|
if order := av.Cmp(bv); order != 0 {
|
|
if kind == WorkbenchPayLow {
|
|
return order < 0
|
|
}
|
|
return order > 0
|
|
}
|
|
if a.ID != b.ID {
|
|
return a.ID < b.ID
|
|
}
|
|
return a.SourceDB < b.SourceDB
|
|
})
|
|
actual, err := uc.Ranks(accountContext(7, 901), []string{"lx", "hw", "yq"}, "last30", kind)
|
|
if err != nil || len(actual) != 10 {
|
|
t.Fatalf("rank failed: %v", err)
|
|
}
|
|
for i, row := range actual {
|
|
if row.AnalyticsKey != expected[i].AnalyticsKey || row.Value != expected[i].Value || row.Name != expected[i].Name || row.Rank != i+1 {
|
|
t.Fatalf("%s rank %d differs from full stable sort: %+v vs %+v", kind, i, row, expected[i])
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func (r *workbenchRepoFake) FillRankNames(_ context.Context, rows []*WorkbenchRankRow) error {
|
|
r.named = rows
|
|
return nil
|
|
}
|
|
|
|
func (r *workbenchRepoFake) Summary(_ context.Context, s AnalyticsScope, _ []string, w WorkbenchWindow) (*WorkbenchTotals, error) {
|
|
r.window, r.scope = w, s
|
|
c := *r.totals
|
|
return &c, nil
|
|
}
|
|
func (r *workbenchRepoFake) Ranks(_ context.Context, s AnalyticsScope, f AnalyticsFilter, _ WorkbenchRankKind) (*WorkbenchRankData, error) {
|
|
r.filter, r.scope = f, s
|
|
out := &WorkbenchRankData{Available: r.data.Available}
|
|
for _, row := range r.data.Rows {
|
|
c := *row
|
|
out.Rows = append(out.Rows, &c)
|
|
}
|
|
return out, nil
|
|
}
|
|
func workbenchFixture() (*WorkbenchUsecase, *workbenchRepoFake) {
|
|
r := &workbenchRepoFake{totals: &WorkbenchTotals{Available: true, Today: WorkbenchDay{Counts: PortraitCounts{Register: 3, Active: 4}, Pay: "20.005", ActiveChannels: 2, NewChannels: 1}, Yesterday: WorkbenchDay{Counts: PortraitCounts{Register: 2}, Pay: "10", ActiveChannels: 1}, MonthPay: "300", PreviousMonthPay: "200"}, data: &WorkbenchRankData{Available: true}}
|
|
a := &accountRepoFake{accounts: map[uint]*Account{7: {ID: 7, Level: 1, IDPath: "0"}}}
|
|
uc := NewWorkbenchUsecase(r, NewAccountUsecase(a), NewUserPreferenceUsecase(&channelPreferenceFake{UserPreference{IncludeSubPromote: true, IDCardDedup: true}}))
|
|
uc.now = func() time.Time { return time.Date(2026, 3, 31, 9, 0, 0, 0, portraitLocation) }
|
|
return uc, r
|
|
}
|
|
func TestWorkbenchSummaryCalendarAndRatios(t *testing.T) {
|
|
uc, r := workbenchFixture()
|
|
out, err := uc.Summary(accountContext(7, 901), []string{"lx"})
|
|
if err != nil || *out.TodayRegister.Value != "3.00" || *out.TodayRegister.ChangeRate != "50.0" || out.TodayActive.ChangeRate != nil || *out.TodayPay.Value != "20.01" || *out.TodayPay.ChangeRate != "100.1" || *out.MonthPay.ChangeRate != "50.0" || *out.ActivePromote.ChangeRate != "100.0" {
|
|
t.Fatalf("卡片精度或环比错误: %+v %v", out, err)
|
|
}
|
|
if r.window.PreviousMonthEnd.Format("2006-01-02") != "2026-02-28" || r.window.PreviousMonthStart.Format("2006-01-02") != "2026-02-01" {
|
|
t.Fatal("上月同期需要截断至月末")
|
|
}
|
|
r.totals.Available = false
|
|
out, err = uc.Summary(accountContext(7, 901), []string{"lx"})
|
|
if err != nil || out.TodayPay.Value != nil || out.TodayPay.CompareValue != nil || out.Available {
|
|
t.Fatal("缺表不能返回零卡片")
|
|
}
|
|
if _, err := uc.Summary(accountContext(7, 901), nil); err != ErrWorkbenchSource {
|
|
t.Fatal("必须选择平台")
|
|
}
|
|
}
|
|
func TestWorkbenchRankLimitsExactSortDefaultsAndUnlogin(t *testing.T) {
|
|
uc, r := workbenchFixture()
|
|
ctx := accountContext(7, 901)
|
|
for i := 0; i < 14; i++ {
|
|
r.data.Rows = append(r.data.Rows, &WorkbenchRankRow{AnalyticsKey: AnalyticsKey{SourceDB: "lx", ID: int64(i)}, Value: fmt.Sprint(i)})
|
|
}
|
|
rows, err := uc.Ranks(ctx, []string{"lx"}, "", WorkbenchPayHigh)
|
|
if err != nil || len(rows) != 10 || rows[0].ID != 13 || rows[9].Rank != 10 {
|
|
t.Fatal("榜单排序或 TOP10 截断错误")
|
|
}
|
|
if len(r.named) != 10 {
|
|
t.Fatal("仅应补充已入选 TOP10 的渠道名称")
|
|
}
|
|
rows, err = uc.Ranks(ctx, []string{"lx"}, "", WorkbenchPayLow)
|
|
if err != nil || rows[0].ID != 0 || rows[0].Value != "0.00" || rows[0].Name != "官方渠道" || r.filter.Begin != "2026-03-02" {
|
|
t.Fatal("最低榜应包含零值,默认近30天")
|
|
}
|
|
r.data.Rows = []*WorkbenchRankRow{{AnalyticsKey: AnalyticsKey{SourceDB: "lx", ID: 1}, Value: "9007199254740993.00"}, {AnalyticsKey: AnalyticsKey{SourceDB: "hw", ID: 2}, Value: "9007199254740992.99"}}
|
|
rows, err = uc.Ranks(ctx, []string{"lx", "hw"}, "last7", WorkbenchPayHigh)
|
|
if err != nil || rows[0].ID != 1 || r.filter.Begin != "2026-03-25" {
|
|
t.Fatal("金额排序丢失精度或近7天范围错误")
|
|
}
|
|
if _, err := uc.Ranks(ctx, []string{"lx"}, "invalid", WorkbenchRegister); err != ErrWorkbenchPeriod {
|
|
t.Fatal("无效时间维度必须拒绝")
|
|
}
|
|
today := workbenchWindow(uc.now()).Today
|
|
created := today.AddDate(0, 0, -5).Unix()
|
|
future := today.AddDate(0, 0, 1).Unix()
|
|
r.data.Rows = []*WorkbenchRankRow{{AnalyticsKey: AnalyticsKey{ID: 1}}, {AnalyticsKey: AnalyticsKey{ID: 2}, CreatedUnix: &created}, {AnalyticsKey: AnalyticsKey{ID: 3}, CreatedUnix: &created, LastLoginUnix: &future}}
|
|
rows, err = uc.Ranks(ctx, []string{"lx"}, "ignored", WorkbenchUnlogin)
|
|
if err != nil || rows[0].Value != "99999" || rows[1].Value != "5" || rows[2].Value != "0" {
|
|
t.Fatal("未登录回退创建时间、无时间或未来时间错误")
|
|
}
|
|
r.data.Available = false
|
|
if _, err := uc.Ranks(ctx, []string{"lx"}, "", WorkbenchUnlogin); err != ErrWorkbenchUnavailable {
|
|
t.Fatal("缺数据不能返回空榜单")
|
|
}
|
|
}
|