package event import ( "database/sql" "strings" "testing" "git.yeij.top/AskaEth/Scoreboard/server/internal/store" ) // 0023:聚合通道(basis=aggregate)的汇总口径 aggregate_mode。 // // 三条底线: // - 老库升级后每一行都拿到默认值 avg —— 升级前只有「按成员平均」一种口径,榜单必须一分不差; // - 其它列与行数一行不动(这是纯加列迁移); // - 取值只允许 avg / sum,拼错的取值在写侧就被 CHECK 挡下; // - 幂等:连跑两次不报错、不重复登记。 func TestMigration0023ChannelAggregateMode(t *testing.T) { db := openEventDB(t) applyBefore(t, db, "0023") // 回到「升级前」:库结构到 0022 为止 var teamProfile int64 if err := db.QueryRow(`SELECT id FROM scoring_profiles WHERE code = 'team_top6'`).Scan(&teamProfile); err != nil { t.Fatalf("读取团体积分模板: %v", err) } stmts := []string{ `INSERT INTO teams(code, name) VALUES ('T1', '第一分公司')`, `INSERT INTO sports(code, name) VALUES ('SP1', '田径')`, `INSERT INTO events(sport_id, code, name, event_type, result_mode, result_type, compare, scored, multiplier, status) VALUES (1, 'TEAM_PULLUP', '团体引体向上', 'individual_team', 'auto', 'count', 'desc', 1, 2, 'published')`, } for _, s := range stmts { if _, err := db.Exec(s); err != nil { t.Fatalf("造老库数据 %q: %v", s, err) } } if _, err := db.Exec(`INSERT INTO event_score_channels(event_id, scope, basis, profile_id, sort_order, count_to_team) VALUES (1, 'team', 'aggregate', ?, 1, 1)`, teamProfile); err != nil { t.Fatalf("造老库通道: %v", err) } // 升级前:这一列还不该存在。 var probe sql.NullString if err := db.QueryRow(`SELECT aggregate_mode FROM event_score_channels`).Scan(&probe); err == nil { t.Fatal("升级前不应存在 aggregate_mode 列") } else if !strings.Contains(err.Error(), "aggregate_mode") { t.Fatalf("查询报错应点明缺列 aggregate_mode,实际 %v", err) } if err := store.MigrateEvent(db); err != nil { t.Fatalf("升级赛事库: %v", err) } // 老行取默认值 avg,其它列原样。 var ( scope, basis, mode string countToTeam int sortOrder int ) if err := db.QueryRow(`SELECT scope, basis, aggregate_mode, count_to_team, sort_order FROM event_score_channels WHERE event_id = 1`). Scan(&scope, &basis, &mode, &countToTeam, &sortOrder); err != nil { t.Fatalf("读取升级后的通道: %v", err) } if scope != "team" || basis != "aggregate" || countToTeam != 1 || sortOrder != 1 { t.Fatalf("升级不应改动既有列:scope=%q basis=%q count_to_team=%d sort_order=%d", scope, basis, countToTeam, sortOrder) } if mode != "avg" { t.Fatalf("老通道的汇总口径 = %q,期望默认值 avg(等于升级前的「按成员平均」)", mode) } // 取值只有 avg / sum:拼错的必须被 CHECK 挡下,而不是静默当成某个口径。 if _, err := db.Exec(`INSERT INTO event_score_channels(event_id, scope, basis, profile_id, sort_order, aggregate_mode) VALUES (1, 'athlete', 'aggregate', ?, 2, 'biggest')`, teamProfile); err == nil { t.Fatal("非法 aggregate_mode 应被 CHECK 拒绝") } if _, err := db.Exec(`INSERT INTO event_score_channels(event_id, scope, basis, profile_id, sort_order, aggregate_mode) VALUES (1, 'athlete', 'aggregate', ?, 2, 'sum')`, teamProfile); err != nil { t.Fatalf("写入 sum 口径应成功: %v", err) } // 幂等:再跑一次不报错、行数不变、登记仍只有一条。 if err := store.MigrateEvent(db); err != nil { t.Fatalf("重复升级应无副作用: %v", err) } var rows, registered int if err := db.QueryRow(`SELECT COUNT(*) FROM event_score_channels`).Scan(&rows); err != nil { t.Fatalf("统计通道: %v", err) } if rows != 2 { t.Fatalf("通道数 = %d,期望 2", rows) } if err := db.QueryRow(`SELECT COUNT(*) FROM schema_migrations WHERE name = '0023_channel_aggregate_mode.sql'`).Scan(®istered); err != nil { t.Fatalf("查询迁移登记: %v", err) } if registered != 1 { t.Fatalf("迁移登记数 = %d,期望 1", registered) } }