fix: 修掉十路审查发现的 9 个 P0 与一批 P1(数据正确性 / 越权 / 现场可用性)
P0(9 条全修): - 多通道项目奖牌重复计数:domain 层按 (EventID,CategoryID,EntryID/Subject) 去重, 通道优先级 athlete→team→id - 「只排名不计分」项目(scored:false)不再进奖牌榜,REST/WS/导出口径统一 - aggregate 流水 entry_id 恒 NULL 导致重算后分数翻倍:作废范围改按 event+category 定位 - 已发布成绩仍可被「存草稿」覆盖并清空 rank:服务端 409 STATE_INVALID + 前端禁编辑 - voidResult / 规则导入重算后名次不回写:接住 Ranked 调 applyComputedRanks - 赛事包覆盖导入可能「旧项目已删、新项目没成」:先改名成功再清理,失败回滚 - 导入赛事包可覆盖正在运行的赛事目录:name==当前项目直接 400 - Console spawn 失败后守护器永久卡死:清 child/pid + 调度重启 + stop 超时兜底 - /ws 快照向非 admin 下发 admin-only 配置:按角色裁剪 settings P1(计分口径 / 契约 / 现场): - standings.tieBreak 三处接线;profiles[].recordBonusMode 生效;破纪录校验 RecordEligible 与 entry 归属;破纪录登记校验已发布成绩 - export.loadRanked 与主路径对齐;ListBackups reason 解析;CLI backup 前缀修正 (原来只增不减会撑爆磁盘);单位积分明细/导出统一权重折算;停用单位展示 - 规则导入原子化(重算并入同一事务)+ 返回 compatible/affectedEventCount/ recalculatedLedgerRows;登录审计 user_id 修正 - WS 策略性断开用 Close(code,reason);HelloMessage 补 page/instanceId; 实例注册表清理;settings/单位/项目写操作补 change_log 触发大屏刷新 - 大屏:全量快照不再被版本守卫丢弃;个人积分榜刷新;公告返回页/轮播;行数截断; REST 竞态;1008 停止重连;握手超时;时区补 Z;401 仅 AUTH_REQUIRED 才登出 - 管理端:published 行禁编辑;saveDraft 补 unit(1:06.33 显示);前端预览改用服务端 points;空成绩行不再恒「已修改」;修订详情按 id 拉取;切项目请求序号与 dirty 保护; 发布后刷新项目状态;NAV/路由按角色过滤;recorder 不再打 admin-only 接口 - Console:大屏窗口销毁后重建;大屏 URL 复用 host 归一化;backup.bat 改纯 ASCII (已在 Meowhome 实跑验证) 自检:服务端 gofmt/build/vet 通过、12 包测试全绿;前端 vue-tsc + build 通过、 node --test 24/24;console check-syntax 12 文件通过。
This commit is contained in:
1 parent
96cf181ae3
commit
13f1f03cd0
124 files changed
+5658
-1820
No files matched your search
@@ -194,6 +194,123 @@ func TestRecordBonusLedger(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
// recordBonusPointsOf 统计一次计分产出的破纪录加分总和。
|
||||
func recordBonusPointsOf(t *testing.T, in EventScoreInput) float64 {
|
||||
t.Helper()
|
||||
var got float64
|
||||
for _, e := range mustScore(t, in).Ledger {
|
||||
if e.SourceType == SourceRecordBonus {
|
||||
got += e.Points
|
||||
}
|
||||
}
|
||||
return got
|
||||
}
|
||||
|
||||
// TestRecordBonusRequiresEligibleEvent 验证只有「可破纪录」的项目才产生加分(P1-5)。
|
||||
func TestRecordBonusRequiresEligibleEvent(t *testing.T) {
|
||||
entries, results := fixture(lineup(1))
|
||||
cfg := bonusConfig(RecordBonusMax)
|
||||
in := EventScoreInput{
|
||||
Event: individualEvent(1), Category: testCategory(),
|
||||
Channels: []ScoreChannel{athleteResultChannel(1)},
|
||||
Profiles: map[int64]ScoringProfile{1: individualTop6()},
|
||||
Entries: entries, Results: results,
|
||||
RecordBonus: &cfg,
|
||||
RecordBreaks: []RecordBreak{{
|
||||
ID: 1, EventID: 1, EntryID: 1, AthleteID: 101, TeamID: 1001,
|
||||
Level: "meet", State: RecordBreakApproved,
|
||||
}},
|
||||
}
|
||||
|
||||
in.Event.RecordEligible = false
|
||||
if got := recordBonusPointsOf(t, in); got != 0 {
|
||||
t.Fatalf("不可破纪录项目的加分 = %v,期望 0", got)
|
||||
}
|
||||
|
||||
in.Event.RecordEligible = true
|
||||
if got := recordBonusPointsOf(t, in); !nearlyEqual(got, 2) {
|
||||
t.Fatalf("可破纪录项目的加分 = %v,期望 2", got)
|
||||
}
|
||||
}
|
||||
|
||||
// TestRecordBonusUsesHitChannelProfileMode 验证 max/sum 口径取「命中通道」所属模板的配置,
|
||||
// 而不是全局 cfg.Mode(P1-3)。
|
||||
func TestRecordBonusUsesHitChannelProfileMode(t *testing.T) {
|
||||
entries, results := fixture(lineup(1))
|
||||
athleteProfile := individualTop6()
|
||||
athleteProfile.RecordBonusMode = RecordBonusSum
|
||||
teamProfile := teamTop6()
|
||||
teamProfile.RecordBonusMode = RecordBonusMax
|
||||
cfg := bonusConfig(RecordBonusMax)
|
||||
in := EventScoreInput{
|
||||
Event: individualEvent(1), Category: testCategory(),
|
||||
// 团体通道排在前面、通道 id 更小,个人通道仍应优先命中。
|
||||
Channels: []ScoreChannel{
|
||||
{ID: 2, EventID: 1, Scope: ScopeTeam, Basis: BasisResult, ProfileID: 2},
|
||||
{ID: 1, EventID: 1, Scope: ScopeAthlete, Basis: BasisResult, ProfileID: 1},
|
||||
},
|
||||
Profiles: map[int64]ScoringProfile{1: athleteProfile, 2: teamProfile},
|
||||
Entries: entries, Results: results,
|
||||
RecordBonus: &cfg,
|
||||
RecordBreaks: []RecordBreak{
|
||||
{ID: 1, EventID: 1, EntryID: 1, AthleteID: 101, TeamID: 1001, Level: "meet", State: RecordBreakApproved},
|
||||
{ID: 2, EventID: 1, EntryID: 1, AthleteID: 101, TeamID: 1001, Level: "city", State: RecordBreakApproved},
|
||||
},
|
||||
}
|
||||
if got := recordBonusPointsOf(t, in); !nearlyEqual(got, 7) {
|
||||
t.Fatalf("加分 = %v,期望 7(个人通道 sum:2+5)", got)
|
||||
}
|
||||
}
|
||||
|
||||
// TestRecordBonusProfileModeOverridesGlobal 验证模板口径优先于全局口径。
|
||||
func TestRecordBonusProfileModeOverridesGlobal(t *testing.T) {
|
||||
entries, results := fixture(lineup(1))
|
||||
profile := individualTop6()
|
||||
profile.RecordBonusMode = RecordBonusMax
|
||||
cfg := bonusConfig(RecordBonusSum)
|
||||
in := EventScoreInput{
|
||||
Event: individualEvent(1), Category: testCategory(),
|
||||
Channels: []ScoreChannel{athleteResultChannel(1)},
|
||||
Profiles: map[int64]ScoringProfile{1: profile},
|
||||
Entries: entries, Results: results,
|
||||
RecordBonus: &cfg,
|
||||
RecordBreaks: []RecordBreak{
|
||||
{ID: 1, EventID: 1, EntryID: 1, AthleteID: 101, TeamID: 1001, Level: "meet", State: RecordBreakApproved},
|
||||
{ID: 2, EventID: 1, EntryID: 1, AthleteID: 101, TeamID: 1001, Level: "city", State: RecordBreakApproved},
|
||||
},
|
||||
}
|
||||
if got := recordBonusPointsOf(t, in); !nearlyEqual(got, 5) {
|
||||
t.Fatalf("加分 = %v,期望 5(模板 max 优先于全局 sum)", got)
|
||||
}
|
||||
}
|
||||
|
||||
// TestRecordBonusTeamEntryUsesTeamChannelMode 验证没有运动员的报名项命中团体通道的模板口径。
|
||||
func TestRecordBonusTeamEntryUsesTeamChannelMode(t *testing.T) {
|
||||
entries, results := fixture([]participant{{entryID: 1, teamID: 1001, value: 1, rank: 1}})
|
||||
athleteProfile := individualTop6()
|
||||
athleteProfile.RecordBonusMode = RecordBonusMax
|
||||
teamProfile := teamTop6()
|
||||
teamProfile.RecordBonusMode = RecordBonusSum
|
||||
cfg := bonusConfig(RecordBonusMax)
|
||||
in := EventScoreInput{
|
||||
Event: teamEvent(1), Category: testCategory(),
|
||||
Channels: []ScoreChannel{
|
||||
{ID: 1, EventID: 1, Scope: ScopeAthlete, Basis: BasisResult, ProfileID: 1},
|
||||
{ID: 2, EventID: 1, Scope: ScopeTeam, Basis: BasisResult, ProfileID: 2},
|
||||
},
|
||||
Profiles: map[int64]ScoringProfile{1: athleteProfile, 2: teamProfile},
|
||||
Entries: entries, Results: results,
|
||||
RecordBonus: &cfg,
|
||||
RecordBreaks: []RecordBreak{
|
||||
{ID: 1, EventID: 1, EntryID: 1, TeamID: 1001, Level: "meet", State: RecordBreakApproved},
|
||||
{ID: 2, EventID: 1, EntryID: 1, TeamID: 1001, Level: "city", State: RecordBreakApproved},
|
||||
},
|
||||
}
|
||||
if got := recordBonusPointsOf(t, in); !nearlyEqual(got, 7) {
|
||||
t.Fatalf("加分 = %v,期望 7(团体通道 sum:2+5)", got)
|
||||
}
|
||||
}
|
||||
|
||||
// TestRecordBonusLedgerFiltersByCategory 验证按「小项+组别」过滤:
|
||||
// 上层逐组别调用 ScoreEvent,别的组别的破纪录登记不能在本组别重复记一次加分。
|
||||
func TestRecordBonusLedgerFiltersByCategory(t *testing.T) {
|
||||
|
||||
Reference in new issue
Block a user