134 lines
5.3 KiB
Go
134 lines
5.3 KiB
Go
package business
|
|
|
|
import (
|
|
"context"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"gorm.io/gorm"
|
|
"gorm.io/gorm/logger"
|
|
"kra/internal/biz/business"
|
|
)
|
|
|
|
type alertSQLCapture struct {
|
|
logger.Interface
|
|
queries []string
|
|
elapsed []time.Duration
|
|
}
|
|
|
|
func (l *alertSQLCapture) Trace(ctx context.Context, begin time.Time, fc func() (string, int64), err error) {
|
|
sql, _ := fc()
|
|
if strings.Contains(sql, "AS current_value") || strings.Contains(sql, "AS first_unix") || strings.Contains(sql, "AS recent_amount") {
|
|
l.queries = append(l.queries, sql)
|
|
l.elapsed = append(l.elapsed, time.Since(begin))
|
|
}
|
|
l.Interface.Trace(ctx, begin, fc, err)
|
|
}
|
|
|
|
func TestMySQLAlertV2MigrationAndRangePlans(t *testing.T) {
|
|
db := openRehearsalMySQL(t)
|
|
now := prepareAlertV2(t, db)
|
|
// 模拟尚未升级的真实旧表,覆盖普通列合并迁移路径。
|
|
for _, sql := range []string{
|
|
"ALTER TABLE sys_alert_record DROP INDEX uk_alert_record_v2_open",
|
|
"ALTER TABLE sys_alert_record DROP COLUMN v2_open_key",
|
|
"ALTER TABLE sys_alert_record DROP COLUMN independent_rule, DROP COLUMN total_players, DROP COLUMN total_pay, DROP COLUMN warning_color",
|
|
"ALTER TABLE sys_alert_rule DROP COLUMN condition_mode, DROP COLUMN amount_direction, DROP COLUMN amount_threshold",
|
|
} {
|
|
if err := db.Exec(sql).Error; err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
|
|
if err := db.Exec("ALTER TABLE sys_alert_record ADD COLUMN unclosed_flag TINYINT GENERATED ALWAYS AS (CASE WHEN status=5 THEN NULL ELSE 1 END) STORED, ADD UNIQUE INDEX uk_alert_record_unclosed(platform_code,target_promote_id,rule_type,unclosed_flag)").Error; err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := migrateAlertV2(db); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := migrateAlertV2(db); err != nil {
|
|
t.Fatalf("重复迁移: %v", err)
|
|
}
|
|
if db.Migrator().HasIndex("sys_alert_record", "uk_alert_record_unclosed") {
|
|
t.Fatal("旧唯一键阻挡已忽略或独立绝对值规则")
|
|
}
|
|
if err := db.Exec("ALTER TABLE sys_alert_record DROP INDEX uk_alert_record_v2_open, ADD UNIQUE INDEX uk_alert_record_v2_open(platform_code,target_promote_id,rule_type,v2_open_key)").Error; err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := migrateAlertV2UniqueKey(db); err != nil {
|
|
t.Fatalf("已有 V2 索引升级失败: %v", err)
|
|
}
|
|
rows := make([]map[string]any, 10000)
|
|
for i := range rows {
|
|
rows[i] = map[string]any{"id": 100 + i, "source_db": "lx", "promote_id": 1, "pay_status": 1, "pay_time": now.AddDate(-1, 0, 0).Unix(), "pay_amount": "1.00"}
|
|
}
|
|
if err := db.Table("tab_spend").CreateInBatches(rows, 500).Error; err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := db.Exec("ANALYZE TABLE tab_spend,tab_promote").Error; err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
capture := &alertSQLCapture{Interface: logger.Default.LogMode(logger.Silent)}
|
|
r := NewAlertTriggerRepo(testData{db.Session(&gorm.Session{Logger: capture})})
|
|
key := business.ChannelKey{SourceDB: "lx", ID: 1}
|
|
values, err := r.Comparison(context.Background(), 2, []business.ChannelKey{key}, now, 1)
|
|
if err != nil || values[key].Current != "3000.00" || values[key].Previous != "100.00" {
|
|
t.Fatalf("MySQL 周期计算: %+v %v", values, err)
|
|
}
|
|
if _, err := loadAlertLoss(db.Session(&gorm.Session{Logger: capture}), []business.ChannelKey{key, key}, business.AlertLossAt(now)); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
for i, sql := range capture.queries {
|
|
var plan []struct {
|
|
Table string
|
|
Type string
|
|
Key string
|
|
Rows int64
|
|
Extra string
|
|
}
|
|
if err := db.Raw("EXPLAIN " + sql).Scan(&plan).Error; err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
t.Logf("query %d elapsed=%v plan=%+v", i, capture.elapsed[i], plan)
|
|
for _, step := range plan {
|
|
if (strings.Contains(sql, "AS current_value") || strings.Contains(sql, "AS recent_amount")) && (step.Table == "m" || step.Table == "tab_spend") && (step.Type != "range" || step.Rows > 100) {
|
|
t.Fatalf("预警周期统计必须按时间范围读取,不能扫描整个渠道历史: %+v", plan)
|
|
}
|
|
}
|
|
}
|
|
hit := &business.AlertRecord{PlatformCode: "lx", TargetPromoteID: 1, RuleID: 1, RuleType: "充值", IndependentRule: true, GapDays: 7, Status: 1, CreatedAt: now, UpdatedAt: now}
|
|
decide := func(r *business.AlertRecord) bool { return r == nil || r.Status == 1 }
|
|
for _, id := range []uint64{1, 2} {
|
|
hit.RuleID = id
|
|
if ok, err := r.StoreHit(context.Background(), hit, []int{1, 2, 3, 4}, decide); err != nil || !ok {
|
|
t.Fatalf("独立规则被唯一键挡住: %v %v", ok, err)
|
|
}
|
|
}
|
|
if err := db.Model(&alertRecordPO{}).Where("rule_id=1").Update("status", 6).Error; err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
hit.RuleID = 1
|
|
hit.CreatedAt = now.AddDate(0, 0, 7)
|
|
if ok, err := r.StoreHit(context.Background(), hit, []int{1, 2, 3, 4}, decide); err != nil || !ok {
|
|
t.Fatalf("已忽略仍占用唯一键: %v %v", ok, err)
|
|
}
|
|
}
|
|
|
|
func TestMySQLAlertMigrationKeepsLegacyConstraintOnUnexpectedIndex(t *testing.T) {
|
|
db := openRehearsalMySQL(t)
|
|
for _, sql := range []string{
|
|
"ALTER TABLE sys_alert_record DROP INDEX uk_alert_record_v2_open, ADD INDEX uk_alert_record_v2_open(platform_code), ADD UNIQUE INDEX uk_alert_record_daily(platform_code,target_promote_id,rule_type,alert_date)",
|
|
} {
|
|
if err := db.Exec(sql).Error; err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
if err := migrateAlertV2UniqueKey(db); err == nil {
|
|
t.Fatal("意外的现有索引定义不应被当成迁移成功")
|
|
}
|
|
if !db.Migrator().HasIndex("sys_alert_record", "uk_alert_record_daily") {
|
|
t.Fatal("验证失败前不能移除旧约束")
|
|
}
|
|
}
|