package business import ( "context" "testing" "time" ) type ledgerRepoFake struct { LedgerRepo scope LedgerScope filter LedgerFilter options LedgerListOptions total int64 available bool gameLimit int } func (r *ledgerRepoFake) Registrations(_ context.Context, s LedgerScope, f LedgerFilter, o LedgerListOptions) (*RegistrationPage, error) { r.scope = s r.filter = f r.options = o return &RegistrationPage{Rows: []*Registration{}, Total: r.total, Available: r.available}, nil } func (r *ledgerRepoFake) Spends(_ context.Context, s LedgerScope, f LedgerFilter, o LedgerListOptions) (*SpendPage, error) { r.scope = s r.filter = f r.options = o return &SpendPage{Rows: []*Spend{}, Total: r.total, Available: r.available, TotalPayAmount: stringPointer("0")}, nil } func (r *ledgerRepoFake) Games(_ context.Context, viewer UserScope, _ []string, _ string, limit int) ([]*GameOption, error) { r.scope.Viewer = viewer r.gameLimit = limit return []*GameOption{}, nil } func ledgerFixture() (*LedgerUsecase, *ledgerRepoFake, *channelPreferenceFake) { a := &accountRepoFake{accounts: map[uint]*Account{1: {ID: 1, Level: 0}, 7: {ID: 7, Level: 1, IDPath: "0"}, 9: {ID: 9, Level: 3, IDPath: "0,7"}}} r := &ledgerRepoFake{available: true} p := &channelPreferenceFake{UserPreference{IncludeSubPromote: true, IDCardDedup: true}} uc := NewLedgerUsecase(r, NewAccountUsecase(a), NewUserPreferenceUsecase(p)) uc.now = func() time.Time { return time.Date(2026, 11, 4, 14, 0, 0, 0, portraitLocation) } return uc, r, p } func TestLedgerDateDefaultsAndBusinessScope(t *testing.T) { uc, r, p := ledgerFixture() if _, err := uc.Registrations(accountContext(7, 901), LedgerFilter{SortField: "payAmount"}, LedgerListOptions{Limit: 10}); err != nil { t.Fatal(err) } if time.Unix(r.filter.BeginUnix, 0).In(portraitLocation).Format("2006-01-02 15:04:05") != "2026-10-06 00:00:00" || time.Unix(r.filter.EndUnix, 0).In(portraitLocation).Format("2006-01-02 15:04:05") != "2026-11-04 23:59:59" { t.Fatal("未传日期应取含今天的近30天") } if r.scope.Unrestricted || !r.scope.Viewer.IncludeSelf || r.filter.SortField != "totalPayAmount" { t.Fatal("商务范围或排序别名错误") } p.pref = UserPreference{IncludeSubPromote: false, IDCardDedup: false} uc.Spends(accountContext(9, 903), LedgerFilter{}, LedgerListOptions{Limit: 10}) if !r.scope.Viewer.OnlySelf || r.scope.IncludeSub { t.Fatal("三级本人范围或下级开关丢失") } uc.Spends(accountContext(1, 888), LedgerFilter{}, LedgerListOptions{Limit: 10}) if !r.scope.Unrestricted { t.Fatal("超管默认全平台") } uc.Spends(accountContext(1, 888), LedgerFilter{BusierAccount: "商务"}, LedgerListOptions{Limit: 10}) if r.scope.Unrestricted { t.Fatal("超管指定商务后必须收窄") } } func TestLedgerExportLimitAndDisplayFallback(t *testing.T) { uc, r, _ := ledgerFixture() r.total = 10001 if _, err := uc.ExportRegistrations(accountContext(7, 901), LedgerFilter{}); err != ErrLedgerExportLimit || r.options.RejectAbove != 10000 { t.Fatal("导出没有执行一万条上限") } r.total = 0 r.available = false if _, err := uc.ExportSpends(accountContext(7, 901), LedgerFilter{}); err != ErrLedgerUnavailable { t.Fatal("缺数据不能导出空文件冒充成功") } zero, unknown := int64(0), int64(99) for _, tc := range []struct { names LedgerNames want string }{{LedgerNames{PromoteID: &zero}, "官方渠道"}, {LedgerNames{PromoteID: &unknown}, "未知渠道(ID:99)"}, {LedgerNames{}, "未知渠道"}, {LedgerNames{PromoteID: &zero, PromoteAccount: "历史账号"}, "历史账号"}} { if tc.names.DisplayPromote() != tc.want { t.Fatal("渠道展示回退错误") } } uc.Games(accountContext(1, 888), nil, "", 51) if r.gameLimit != 10000 { t.Fatal("原游戏选项的特殊批量上限丢失") } if !r.scope.Viewer.All { t.Fatal("超管游戏选项范围未传递") } if _, err := uc.Games(accountContext(9, 903), nil, "", 20); err != nil || !r.scope.Viewer.OnlySelf || r.scope.Viewer.All { t.Fatal("普通商务游戏选项必须限定本人范围") } }