- M3 认证/权限/审计/限流 + 全部 REST 接口 - M4 管理面板(MIUIx + 成绩录入/积分榜下钻/大屏控制) - M5a 大屏页面(六页 + 明细覆盖层 + 缩放适配) - M6 WebSocket 实时同步中心 - M7 xlsx 导出 + 赛事包 zip + 备份 - 规则包 YAML 导入导出(差异预览) - 字体 vendor 子集化(4 套 x2 字重) - 集成:realtime/export/rules 接入 main+httpapi;backup 子命令 - 修复:中间件包装 ResponseWriter 丢失 Hijacker,导致 WS 握手 501
104 lines
4.1 KiB
Go
104 lines
4.1 KiB
Go
package realtime
|
|
|
|
import (
|
|
"context"
|
|
"database/sql"
|
|
"encoding/json"
|
|
"strings"
|
|
"testing"
|
|
|
|
"git.yeij.top/AskaEth/Scoreboard/server/internal/domain"
|
|
)
|
|
|
|
// 快照必须与数据库同源:版本号取自 change_log,各列表取真实表,
|
|
// 且空库时序列化为 [] 而不是 null,避免前端判空逻辑踩坑。
|
|
func TestSnapshotContents(t *testing.T) {
|
|
env := newTestEnv(t)
|
|
db := env.db
|
|
|
|
mustExec(t, db, `UPDATE settings SET value = ? WHERE key = 'event.name'`, `"测试运动会"`)
|
|
mustExec(t, db, `INSERT INTO settings(key, value) VALUES ('display.theme', 'dark')`)
|
|
mustExec(t, db, `INSERT INTO teams(code, name, short_name, sort_order, active) VALUES ('T1', '第一单位', '一队', 1, 1)`)
|
|
mustExec(t, db, `INSERT INTO sports(code, name) VALUES ('S1', '田径')`)
|
|
mustExec(t, db, `INSERT INTO events(sport_id, code, name, event_type, result_mode, result_type, compare, status)
|
|
VALUES (1, 'E1', '100米', 'individual', 'auto', 'time', 'asc', 'published')`)
|
|
mustExec(t, db, `INSERT INTO event_categories(event_id, name) VALUES (1, '男子')`)
|
|
mustExec(t, db, `INSERT INTO athletes(name, gender, team_id) VALUES ('张三', '男', 1)`)
|
|
mustExec(t, db, `INSERT INTO entries(event_id, category_id, team_id, athlete_id, bib) VALUES (1, 1, 1, 1, '101')`)
|
|
mustExec(t, db, `INSERT INTO results(event_id, entry_id, rank, result_text, result_value, unit, state, status)
|
|
VALUES (1, 1, 1, '11.58', 11.58, 's', 'published', 'ok')`)
|
|
mustExec(t, db, `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, 9)`)
|
|
mustExec(t, db, `INSERT INTO records(event_id, level, result_value, result_text, holder_name, holder_team_id, achieved_at, is_current, source)
|
|
VALUES (1, 'meet', 11.2, '11.20', '李四', 1, '2025-09-01', 1, 'history')`)
|
|
version := insertChange(t, db, "result", 1, ActionUpsert, `{"rank":1}`)
|
|
|
|
snap, err := env.hub.Snapshot(context.Background())
|
|
if err != nil {
|
|
t.Fatalf("构建快照: %v", err)
|
|
}
|
|
if snap.Version != version {
|
|
t.Fatalf("快照版本应为 %d,得到 %d", version, snap.Version)
|
|
}
|
|
if got := string(snap.Settings["event.name"]); got != `"测试运动会"` {
|
|
t.Fatalf("settings JSON 值应透传,得到 %s", got)
|
|
}
|
|
if got := string(snap.Settings["display.theme"]); got != `"dark"` {
|
|
t.Fatalf("settings 纯文本应编码为 JSON 字符串,得到 %s", got)
|
|
}
|
|
|
|
if len(snap.Teams) != 1 || snap.Teams[0].Code != "T1" || !snap.Teams[0].Active {
|
|
t.Fatalf("单位不符: %+v", snap.Teams)
|
|
}
|
|
if len(snap.Events) != 1 {
|
|
t.Fatalf("小项数应为 1,得到 %d", len(snap.Events))
|
|
}
|
|
ev := snap.Events[0]
|
|
if ev.Name != "100米" || ev.EventType != domain.EventTypeIndividual || !ev.Scored || ev.Compare != domain.CompareAsc {
|
|
t.Fatalf("小项不符: %+v", ev)
|
|
}
|
|
|
|
if len(snap.Standings) != 1 {
|
|
t.Fatalf("榜单行数应为 1,得到 %d", len(snap.Standings))
|
|
}
|
|
row := snap.Standings[0]
|
|
if row.TeamID != 1 || row.Points != 9 || row.Gold != 1 || row.Rank != 1 {
|
|
t.Fatalf("榜单不符: %+v", row)
|
|
}
|
|
|
|
if len(snap.Records) != 1 || snap.Records[0].HolderName != "李四" || snap.Records[0].ResultValue == nil {
|
|
t.Fatalf("纪录不符: %+v", snap.Records)
|
|
}
|
|
if len(snap.LatestResults) != 1 {
|
|
t.Fatalf("最新成绩数应为 1,得到 %d", len(snap.LatestResults))
|
|
}
|
|
lr := snap.LatestResults[0]
|
|
if lr.ResultText != "11.58" || lr.TeamID != 1 || lr.AthleteID != 1 || lr.Rank != 1 || lr.State != "published" {
|
|
t.Fatalf("最新成绩不符: %+v", lr)
|
|
}
|
|
}
|
|
|
|
func TestEmptySnapshotMarshalsEmptyLists(t *testing.T) {
|
|
env := newTestEnv(t)
|
|
snap, err := env.hub.Snapshot(context.Background())
|
|
if err != nil {
|
|
t.Fatalf("构建空快照: %v", err)
|
|
}
|
|
raw, err := json.Marshal(snap)
|
|
if err != nil {
|
|
t.Fatalf("序列化快照: %v", err)
|
|
}
|
|
for _, want := range []string{`"teams":[]`, `"events":[]`, `"standings":[]`, `"records":[]`, `"latestResults":[]`} {
|
|
if !strings.Contains(string(raw), want) {
|
|
t.Fatalf("空快照应包含 %s,实际 %s", want, raw)
|
|
}
|
|
}
|
|
}
|
|
|
|
func mustExec(t *testing.T, db *sql.DB, q string, args ...any) {
|
|
t.Helper()
|
|
if _, err := db.Exec(q, args...); err != nil {
|
|
t.Fatalf("执行 SQL %q: %v", q, err)
|
|
}
|
|
}
|