package business import ( "context" "testing" "time" "kra/internal/biz/business" ) func TestAlertTriggerStorageUsesRawMetricsAndPreservesWorkflow(t *testing.T) { db := openTestDB(t) if err := db.AutoMigrate(&accountPO{}); err != nil { t.Fatal(err) } now := time.Date(2026, 11, 4, 12, 0, 0, 0, time.FixedZone("Asia/Shanghai", 8*3600)) for _, sql := range []string{ "CREATE TABLE tab_promote(id INTEGER,source_db TEXT,busier_id INTEGER,account TEXT,promote_level INTEGER,status INTEGER,last_login_time INTEGER,create_time INTEGER,parent_id INTEGER DEFAULT 0,top_promote_id INTEGER DEFAULT 0)", "CREATE TABLE tab_user(id INTEGER,source_db TEXT,promote_id INTEGER,puid INTEGER,register_time TEXT,idcard TEXT)", "CREATE TABLE tab_spend(source_db TEXT,promote_id INTEGER,pay_time INTEGER,pay_status INTEGER,pay_amount DECIMAL(20,2))", "INSERT INTO sys_users(id,username,level,parent_id,id_path,authority_id) VALUES(10,'一级',1,0,'0',901),(20,'二级',2,10,'0,10',902),(30,'三级',3,20,'0,10,20',903)", "INSERT INTO sys_user_bind_platform(user_id,source_db,platform_busier_id,platform_busier_account,status) VALUES(30,'lx',7,'绑定','0')", "INSERT INTO tab_promote(id,source_db,busier_id,account,promote_level,status,last_login_time,create_time) VALUES(10,'lx',7,'正常渠道',1,1,0,1),(11,'lx',7,'停用渠道',1,0,0,1),(12,'lx',7,'下级渠道',2,1,0,1)", } { if err := db.Exec(sql).Error; err != nil { t.Fatal(err) } } today := time.Date(2026, 11, 4, 0, 0, 0, 0, now.Location()) if err := db.Exec("INSERT INTO tab_user(id,source_db,promote_id,puid,register_time) VALUES(1,'lx',10,0,?),(2,'lx',10,0,?),(3,'lx',10,NULL,?),(4,'lx',10,1,?),(5,'hw',10,0,?)", today.AddDate(0, 0, -2).Unix(), today.AddDate(0, 0, -1).Unix(), today.AddDate(0, 0, -1).Unix(), today.AddDate(0, 0, -1).Unix(), today.AddDate(0, 0, -1).Unix()).Error; err != nil { t.Fatal(err) } if err := db.Exec("INSERT INTO tab_spend VALUES('lx',10,?,1,100),('lx',10,?,1,110),('lx',10,?,0,999),('hw',10,?,1,999)", today.AddDate(0, 0, -2).Unix(), today.AddDate(0, 0, -1).Unix(), today.AddDate(0, 0, -1).Unix(), today.AddDate(0, 0, -1).Unix()).Error; err != nil { t.Fatal(err) } day, direction := 1, 1 threshold := "10" rule := alertRulePO{ID: 1, RuleName: "充值规则", RuleType: "充值", MonitorMetric: 2, CreatorID: 10, StatPeriodDays: &day, MomDirection: &direction, MomThreshold: &threshold, Enabled: 1} if err := db.Create(&rule).Error; err != nil { t.Fatal(err) } r := NewAlertTriggerRepo(testData{db}) input, err := r.Input(context.Background()) if err != nil || len(input.Channels) != 1 { t.Fatalf("渠道查询错误: %+v %v", input, err) } metrics, err := r.Comparison(context.Background(), 1, []business.ChannelKey{{SourceDB: "lx", ID: 10}}, today, 1) if err != nil || metrics[business.ChannelKey{SourceDB: "lx", ID: 10}].Current != "2" { t.Fatalf("主账号、NULL父账号或跨平台口径错误: %+v %v", metrics, err) } uc := business.NewAlertTriggerUsecase(r, nil) result, err := uc.Detect(context.Background(), now) if err != nil || result.Written != 1 { t.Fatalf("首次命中: %+v %v", result, err) } var first alertRecordPO if err := db.First(&first).Error; err != nil { t.Fatal(err) } if first.BusinessID != 10 || first.TargetPromoteID != 10 || first.PlatformCode != "lx" { t.Fatalf("归属错误: %+v", first) } result, err = uc.Detect(context.Background(), now.Add(time.Hour)) var count int64 db.Model(&alertRecordPO{}).Count(&count) if err != nil || result.Written != 1 || count != 1 { t.Fatalf("重复命中必须刷新同一未处理记录: %+v count=%d %v", result, count, err) } if err := db.Model(&alertRecordPO{}).Where("id=?", first.ID).Update("status", 2).Error; err != nil { t.Fatal(err) } result, err = uc.Detect(context.Background(), now.Add(2*time.Hour)) if err != nil || result.Written != 0 { t.Fatalf("处理中预警被覆盖: %+v %v", result, err) } if err := db.Model(&alertRecordPO{}).Where("id=?", first.ID).Update("status", 4).Error; err != nil { t.Fatal(err) } result, err = uc.Detect(context.Background(), now.Add(3*time.Hour)) db.Model(&alertRecordPO{}).Count(&count) if err != nil || result.Written != 0 || count != 1 { t.Fatalf("驳回仍是未关闭记录,不应重复生成: %+v count=%d %v", result, count, err) } if err := db.Model(&alertRecordPO{}).Where("id=?", first.ID).Update("status", 5).Error; err != nil { t.Fatal(err) } result, err = uc.Detect(context.Background(), now.Add(3*time.Hour)) db.Model(&alertRecordPO{}).Count(&count) if err != nil || result.Written != 1 || count != 2 { t.Fatalf("已关闭后应允许新一轮预警: %+v count=%d %v", result, count, err) } } func TestAlertTriggerWriteFailureRollsBack(t *testing.T) { db := openTestDB(t) if err := db.Exec("CREATE TRIGGER reject_alert BEFORE INSERT ON sys_alert_record BEGIN SELECT RAISE(ABORT,'test write failure'); END").Error; err != nil { t.Fatal(err) } r := NewAlertTriggerRepo(testData{db}) hit := &business.AlertRecord{PlatformCode: "lx", TargetPromoteID: 1, RuleType: "注册", Status: 1, CreatedAt: time.Now(), UpdatedAt: time.Now()} _, err := r.StoreHit(context.Background(), hit, []int{1, 2, 3, 4}, func(*business.AlertRecord) bool { return true }) if err == nil { t.Fatal("写入失败应返回错误") } var count int64 db.Model(&alertTriggerLockPO{}).Count(&count) if count != 0 { t.Fatalf("事务未回滚键锁: %d", count) } }