- M3 认证/权限/审计/限流 + 全部 REST 接口 - M4 管理面板(MIUIx + 成绩录入/积分榜下钻/大屏控制) - M5a 大屏页面(六页 + 明细覆盖层 + 缩放适配) - M6 WebSocket 实时同步中心 - M7 xlsx 导出 + 赛事包 zip + 备份 - 规则包 YAML 导入导出(差异预览) - 字体 vendor 子集化(4 套 x2 字重) - 集成:realtime/export/rules 接入 main+httpapi;backup 子命令 - 修复:中间件包装 ResponseWriter 丢失 Hijacker,导致 WS 握手 501
88 lines
3.3 KiB
Go
88 lines
3.3 KiB
Go
package export
|
|
|
|
import (
|
|
"database/sql"
|
|
"path/filepath"
|
|
"testing"
|
|
|
|
"git.yeij.top/AskaEth/Scoreboard/server/internal/store"
|
|
)
|
|
|
|
// openEventDB 建一个临时赛事库并跑完迁移;DSN 与 store.Open 保持一致。
|
|
func openEventDB(t *testing.T) *sql.DB {
|
|
t.Helper()
|
|
|
|
path := filepath.Join(t.TempDir(), "event.db")
|
|
dsn := "file:" + path +
|
|
"?_pragma=journal_mode(WAL)" +
|
|
"&_pragma=foreign_keys(1)" +
|
|
"&_pragma=busy_timeout(5000)"
|
|
|
|
db, err := sql.Open("sqlite", dsn)
|
|
if err != nil {
|
|
t.Fatalf("打开赛事库: %v", err)
|
|
}
|
|
db.SetMaxOpenConns(1)
|
|
t.Cleanup(func() { _ = db.Close() })
|
|
|
|
if err := store.MigrateEvent(db); err != nil {
|
|
t.Fatalf("迁移赛事库: %v", err)
|
|
}
|
|
return db
|
|
}
|
|
|
|
// seedEvent 造一场最小可用赛事:2 个单位、2 名运动员、1 个小项 1 个组别、
|
|
// 2 条已发布成绩与对应积分流水。
|
|
func seedEvent(t *testing.T, db *sql.DB) {
|
|
t.Helper()
|
|
|
|
stmts := []string{
|
|
`INSERT INTO teams(code, name, short_name, sort_order) VALUES ('T1', '第一分公司', '一公司', 1)`,
|
|
`INSERT INTO teams(code, name, short_name, sort_order) VALUES ('T2', '第二分公司', '二公司', 2)`,
|
|
`INSERT INTO athletes(name, gender, team_id) VALUES ('张三', '男', 1)`,
|
|
`INSERT INTO athletes(name, gender, team_id) VALUES ('李四', '男', 2)`,
|
|
`INSERT INTO sports(code, name) VALUES ('SP1', '田径')`,
|
|
`INSERT INTO events(sport_id, code, name, event_type, result_mode, result_type, compare, status)
|
|
VALUES (1, 'E1', '100米跑', 'individual', 'auto', 'time', 'asc', 'published')`,
|
|
`INSERT INTO event_categories(event_id, name, sort_order) VALUES (1, '男子', 1)`,
|
|
`INSERT INTO event_score_channels(event_id, scope, basis, profile_id)
|
|
SELECT 1, 'athlete', 'result', id FROM scoring_profiles WHERE code = 'individual_top6'`,
|
|
`INSERT INTO entries(event_id, category_id, team_id, athlete_id, bib) VALUES (1, 1, 1, 1, '101')`,
|
|
`INSERT INTO entries(event_id, category_id, team_id, athlete_id, bib) VALUES (1, 1, 2, 2, '102')`,
|
|
`INSERT INTO results(event_id, entry_id, rank, result_text, result_value, unit, state)
|
|
VALUES (1, 1, 1, '11.58', 11.58, 's', 'published')`,
|
|
`INSERT INTO results(event_id, entry_id, rank, result_text, result_value, unit, state)
|
|
VALUES (1, 2, 2, '11.90', 11.90, 's', 'published')`,
|
|
`INSERT INTO score_ledger(team_id, athlete_id, event_id, entry_id, result_id, source_type, source_id, points)
|
|
VALUES (1, 1, 1, 1, 1, 'result', 1, 10)`,
|
|
`INSERT INTO score_ledger(team_id, athlete_id, event_id, entry_id, result_id, source_type, source_id, points)
|
|
VALUES (2, 2, 1, 2, 2, 'result', 2, 8)`,
|
|
`UPDATE settings SET value = '测试运动会' WHERE key = 'event.name'`,
|
|
`UPDATE settings SET value = '测试主办方' WHERE key = 'event.organizer'`,
|
|
}
|
|
for _, s := range stmts {
|
|
if _, err := db.Exec(s); err != nil {
|
|
t.Fatalf("造数据 %q: %v", s, err)
|
|
}
|
|
}
|
|
}
|
|
|
|
func countRows(t *testing.T, db *sql.DB, table string) int {
|
|
t.Helper()
|
|
var n int
|
|
if err := db.QueryRow(`SELECT count(*) FROM ` + table).Scan(&n); err != nil {
|
|
t.Fatalf("统计 %s: %v", table, err)
|
|
}
|
|
return n
|
|
}
|
|
|
|
// openSQLite 以只读方式打开一个已存在的库,用于校验备份 / 导入产物可读。
|
|
func openSQLite(t *testing.T, path string) *sql.DB {
|
|
t.Helper()
|
|
db, err := sql.Open("sqlite", "file:"+path+"?mode=ro")
|
|
if err != nil {
|
|
t.Fatalf("打开 %s: %v", path, err)
|
|
}
|
|
return db
|
|
}
|