Files
Scoreboard/server/internal/realtime/snapshot_test.go
T
AskaEth bc8b1ee6c0 feat: M3-M7 全模块落地 + 集成接线
- M3 认证/权限/审计/限流 + 全部 REST 接口
- M4 管理面板(MIUIx + 成绩录入/积分榜下钻/大屏控制)
- M5a 大屏页面(六页 + 明细覆盖层 + 缩放适配)
- M6 WebSocket 实时同步中心
- M7 xlsx 导出 + 赛事包 zip + 备份
- 规则包 YAML 导入导出(差异预览)
- 字体 vendor 子集化(4 套 x2 字重)
- 集成:realtime/export/rules 接入 main+httpapi;backup 子命令
- 修复:中间件包装 ResponseWriter 丢失 Hijacker,导致 WS 握手 501
2026-09-09 19:02:24 +08:00

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)
}
}