package business import ( "context" "errors" "math/big" "path/filepath" "testing" "time" "github.com/glebarez/sqlite" "gorm.io/gorm" "kra/internal/biz/business" ) type activationDatabases struct{ oa *gorm.DB } func (d activationDatabases) Database(name string) (*gorm.DB, error) { if name != "oa" || d.oa == nil { return nil, errors.New("未配置") } return d.oa, nil } func prepareActivations(t *testing.T) (*gorm.DB, *gorm.DB, business.ActivationRepo) { t.Helper() db := prepareAccounts(t) oa, err := gorm.Open(sqlite.Open(filepath.Join(t.TempDir(), "oa.db")), &gorm.Config{}) if err != nil { t.Fatal(err) } pool, _ := oa.DB() t.Cleanup(func() { _ = pool.Close() }) if err := oa.AutoMigrate(&activationPO{}); err != nil { t.Fatal(err) } start := time.Unix(1000, 0).UTC() end := time.Unix(2000, 0).UTC() channel := uint64(100) amount := "30.30" pos := []activationPO{{ID: 1, PromoteID: &channel, PlatformName: "lx", PromoteAccount: "渠道一", BusierAccount: "Own", BindAt: &start, ReachAt: &end, Status: "reached", AccumAmount: &amount}, {ID: 2, PlatformName: "hw", BusierAccount: "own", Status: "pending"}, {ID: 3, PlatformName: "lx", BusierAccount: "Other", Status: "in_progress"}, {ID: 4, PlatformName: "lx", BusierAccount: "Own", Status: "rejected", DeletedAt: &end}} if err := oa.Create(&pos).Error; err != nil { t.Fatal(err) } for _, sql := range []string{ "INSERT INTO sys_user_bind_platform(user_id,source_db,platform_busier_id,platform_busier_account,status) VALUES(7,'lx',1,'Own','0'),(8,'lx',2,'Child','0'),(7,'hw',3,'Pending','3')", "ALTER TABLE tab_promote ADD COLUMN top_promote_id INTEGER", "ALTER TABLE tab_promote ADD COLUMN parent_id INTEGER", "INSERT INTO tab_promote(id,source_db,top_promote_id,parent_id) VALUES(100,'lx',0,0),(101,'lx',0,100),(101,'lx',0,100),(102,'lx',100,101),(103,'lx',200,200),(100,'hw',0,0)", "CREATE TABLE tab_spend(source_db TEXT,promote_id INTEGER,pay_status INTEGER,pay_time INTEGER,game_id INTEGER,game_name TEXT,pay_amount DECIMAL(20,2))", "INSERT INTO tab_spend VALUES('lx',100,1,1000,1,'A',10.10),('lx',101,1,2000,1,'Z',20.20),('lx',102,1,1500,2,'B',3.30),('lx',103,1,1500,1,'X',900),('hw',100,1,1500,1,'X',999),('lx',100,1,999,1,'X',800),('lx',100,1,2001,1,'X',700),('lx',100,0,1500,1,'X',600)", } { if err := db.Exec(sql).Error; err != nil { t.Fatal(err) } } return db, oa, NewActivationRepo(testData{db}, activationDatabases{oa}) } func TestActivationListsReadOAAndRestrictAccounts(t *testing.T) { db, _, repo := prepareActivations(t) ctx := context.Background() accounts, err := repo.OwnAccounts(ctx, 7) if err != nil || len(accounts) != 1 || accounts[0] != (business.ActivationAccount{SourceDB: "lx", Account: "Own"}) { t.Fatalf("本人已通过账号范围错误: %+v %v", accounts, err) } page, err := repo.List(ctx, business.ActivationScope{Accounts: []business.ActivationAccount{{SourceDB: "lx", Account: "OWN"}}}, business.ActivationFilter{}) if err != nil || page.Total != 1 || page.Rows[0].ID != 1 { t.Fatalf("OA 列表或排序错误: %+v %v", page, err) } page, err = repo.List(ctx, business.ActivationScope{All: true}, business.ActivationFilter{PlatformNames: []string{"lx"}, Statuses: []string{"reached"}}) if err != nil || page.Total != 1 || page.Rows[0].AccumAmount != "30.30" { t.Fatalf("平台或状态筛选错误: %+v %v", page, err) } if _, err := repo.Find(ctx, 4); err != business.ErrActivationNotFound { t.Fatal("软删除 OA 记录仍可访问") } // 即使主库有同名表,也不能在 OA 未配置时回退到主库。 if err := db.AutoMigrate(&activationPO{}); err != nil { t.Fatal(err) } missing := NewActivationRepo(testData{db}, activationDatabases{}) page, err = missing.List(ctx, business.ActivationScope{All: true}, business.ActivationFilter{}) if err != nil || page.Available { t.Fatal("OA 缺配置必须明确不可用") } if _, err := missing.Find(ctx, 1); err != business.ErrActivationSource { t.Fatal("OA 缺配置不应读取主库") } } func TestActivationSpendTreePlatformAndInclusiveBounds(t *testing.T) { db, _, repo := prepareActivations(t) end := int64(2000) query := business.ActivationSpendQuery{PlatformName: "lx", PromoteID: 100, Start: 1000, End: &end} items, err := repo.Spend(context.Background(), query) amount := func(value string) string { n, ok := new(big.Rat).SetString(value) if !ok { return "无效金额" } return n.FloatString(2) } if err != nil || len(items) != 2 || items[0].GameName != "Z" || amount(items[0].Amount) != "30.30" || amount(items[1].Amount) != "3.30" { t.Fatalf("渠道树、同号平台、成功支付、起止边界或重复渠道导致金额错误: %+v %v", items, err) } if err := db.Exec("DROP TABLE tab_spend").Error; err != nil { t.Fatal(err) } if _, err := repo.Spend(context.Background(), query); err != business.ErrActivationMetrics { t.Fatal("缺流水表未报告不可用") } query.PromoteID = 404 items, err = repo.Spend(context.Background(), query) if err != nil || len(items) != 0 { t.Fatal("确定没有渠道时应返回真实空流水") } }