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

143 lines
6.8 KiB
Go

package business
import (
"context"
"testing"
"time"
)
type channelRepoFake struct {
ChannelRepo
roots, descendants []*Channel
metrics *ChannelMetricData
scope UserScope
bindings []*Binding
gameScope ChannelGameScope
}
func (r *channelRepoFake) List(_ context.Context, scope UserScope, _ ChannelFilter, _ ...ListOption) (*ChannelPage, error) {
r.scope = scope
rows := []*Channel{}
for _, root := range r.roots {
copy := *root
rows = append(rows, &copy)
}
return &ChannelPage{Rows: rows, Available: true}, nil
}
func (r *channelRepoFake) StatChannels(context.Context, []*Channel) ([]*Channel, error) {
return r.descendants, nil
}
func (r *channelRepoFake) Metrics(context.Context, []ChannelKey, []ChannelKey, bool) (*ChannelMetricData, error) {
return r.metrics, nil
}
func (r *channelRepoFake) Bindings(_ context.Context, scope UserScope, _ string) ([]*Binding, error) {
r.scope = scope
return r.bindings, nil
}
func (r *channelRepoFake) Find(context.Context, UserScope, ChannelKey) (*Channel, error) {
return r.roots[0], nil
}
func (r *channelRepoFake) Games(_ context.Context, scope ChannelGameScope) ([]*ChannelGame, error) {
r.gameScope = scope
return []*ChannelGame{{ID: 2, PayAmount: "10.004"}, {ID: 1, PayAmount: "10.003"}}, nil
}
type channelWriteFake struct{ input *ChannelWrite }
func (w *channelWriteFake) Create(_ context.Context, input *ChannelWrite) (uint64, error) {
w.input = input
return 123, nil
}
type channelEncoderFake struct{ received string }
func (e *channelEncoderFake) Encode(raw string) (string, error) {
e.received = raw
return "encoded", nil
}
type channelPreferenceFake struct{ pref UserPreference }
func (p *channelPreferenceFake) Find(context.Context, uint) (*UserPreference, error) {
copy := p.pref
return &copy, nil
}
func (p *channelPreferenceFake) Save(context.Context, uint, UserPreference) error { return nil }
func channelFixture() (*ChannelUsecase, *channelRepoFake, *channelPreferenceFake, *channelWriteFake, *channelEncoderFake) {
key := func(id uint64) ChannelKey { return ChannelKey{SourceDB: "lx", ID: id} }
r := &channelRepoFake{roots: []*Channel{{ChannelKey: key(100), Level: intPointer(1)}}, descendants: []*Channel{{ChannelKey: key(101), Level: intPointer(2), ParentID: 100, TopPromoteID: 100}, {ChannelKey: key(102), Level: intPointer(3), ParentID: 101, TopPromoteID: 100}, {ChannelKey: key(103), Level: intPointer(3), ParentID: 101}, {ChannelKey: ChannelKey{SourceDB: "hw", ID: 100}, Level: intPointer(1)}}, bindings: []*Binding{{UserID: 9, PlatformKey: PlatformKey{SourceDB: "lx", BusierID: 14}, Status: "0"}}}
r.metrics = &ChannelMetricData{GamesAvailable: true, PlayersAvailable: true, PayAvailable: true, Games: map[ChannelKey]int64{key(100): 2, key(101): 1}, Players: map[ChannelKey]int64{key(100): 1, key(101): 1, key(102): 1, key(103): 1}, Pay: map[ChannelKey]string{key(100): "10.10", key(101): "20.20", key(102): "30.30", key(103): "40.40"}, PlayerCards: []ChannelPlayer{{ChannelKey: key(100), UserID: 1, IDCard: " abcX "}, {ChannelKey: key(101), UserID: 2, IDCard: "abcx"}, {ChannelKey: key(102), UserID: 3, IDCard: ""}, {ChannelKey: key(103), UserID: 4, IDCard: "third"}}}
p := &channelPreferenceFake{UserPreference{IncludeSubPromote: true, IDCardDedup: true}}
w, e := &channelWriteFake{}, &channelEncoderFake{}
a := &accountRepoFake{accounts: map[uint]*Account{1: {ID: 1}, 7: {ID: 7, Level: 1, IDPath: "0"}, 8: {ID: 8, Level: 2, IDPath: "0,7"}, 9: {ID: 9, Level: 3, IDPath: "0,7,8"}}}
uc := NewChannelUsecase(r, w, NewAccountUsecase(a), NewUserPreferenceUsecase(p), e, nil)
uc.now = func() time.Time { return time.Date(2026, 11, 3, 12, 0, 0, 0, time.UTC) }
return uc, r, p, w, e
}
func TestChannelMetricsHonorBothPreferencesWithoutChangingGameCount(t *testing.T) {
uc, _, pref, _, _ := channelFixture()
for _, tc := range []struct {
include, dedup bool
count int64
pay string
}{{true, true, 1, "60.60"}, {true, false, 3, "60.60"}, {false, true, 1, "10.10"}, {false, false, 1, "10.10"}} {
pref.pref = UserPreference{IncludeSubPromote: tc.include, IDCardDedup: tc.dedup}
page, err := uc.List(accountContext(8, 902), ChannelFilter{})
if err != nil {
t.Fatal(err)
}
row := page.Rows[0]
if *row.GameCount != 2 || *row.PlayerCount != tc.count || *row.TotalPayAmount != tc.pay {
t.Fatalf("偏好统计错误 include=%v dedup=%v row=%+v", tc.include, tc.dedup, row)
}
}
}
func TestChannelLevelTwoScopeAndUnavailableMetrics(t *testing.T) {
uc, r, _, _, _ := channelFixture()
r.roots = []*Channel{r.descendants[0]}
page, err := uc.List(accountContext(9, 903), ChannelFilter{})
if err != nil || *page.Rows[0].PlayerCount != 2 || *page.Rows[0].TotalPayAmount != "90.90" || !r.scope.OnlySelf {
t.Fatal("二级渠道仅聚合直属三级或商务范围错误")
}
r.metrics.PlayersAvailable = false
r.metrics.PayAvailable = false
page, err = uc.List(accountContext(9, 903), ChannelFilter{})
if err != nil || page.Rows[0].PlayerCount != nil || page.Rows[0].TotalPayAmount != nil || page.Rows[0].GameCount == nil {
t.Fatal("未接入指标不能伪造零,已接入指标应保留")
}
}
func TestChannelCreateOwnsHashAndSettlementDefaults(t *testing.T) {
uc, r, _, writer, encoder := channelFixture()
input := &ChannelCreate{SourceDB: " LX ", Account: " 中文渠道 ", Password: " pass123 ", BusierID: 14, RegisterType: 1}
result, err := uc.Create(accountContext(8, 902), input)
if err != nil || result.ID != 123 || result.SourceDB != "lx" {
t.Fatalf("创建失败: %+v %v", result, err)
}
if writer.input.Account != "中文渠道" || writer.input.Password != "encoded" || encoder.received != " pass123 " || writer.input.NextCountUnix-writer.input.CreatedUnix != 604800 || writer.input.Status != -3 || writer.input.Level != 1 || writer.input.SettlementDays != 7 || !r.scope.IncludeSelf {
t.Fatalf("创建参数或结算周期错误: %+v", writer.input)
}
input.BusierID = 999
if _, err := uc.Create(accountContext(8, 902), input); err != ErrChannelBinding {
t.Fatal("选择了范围外的绑定")
}
input.BusierID = 14
input.Account = "bad_underscore"
if _, err := uc.Create(accountContext(8, 902), input); err != ErrChannelAccount {
t.Fatal("渠道账号字符限制丢失")
}
input.Account = "valid123"
input.Password = "12345"
if _, err := uc.Create(accountContext(8, 902), input); err != ErrChannelPassword {
t.Fatal("密码长度未校验")
}
}
func TestChannelGamesUseShanghaiDateAndStableMoneyOrder(t *testing.T) {
uc, r, _, _, _ := channelFixture()
uc.now = func() time.Time { return time.Date(2026, 11, 3, 17, 0, 0, 0, time.UTC) }
games, err := uc.Games(accountContext(9, 903), ChannelKey{SourceDB: "lx", ID: 100})
if err != nil || r.gameScope.EndDate != "2026-11-04" || !r.gameScope.IDCardDedup || games[0].ID != 1 {
t.Fatal("累计日期、口径或并列排序错误")
}
}