- domain/rules: 计分与排名解耦、破纪录按组别、tieBreak 生效、调整分不丢、移除 aggregate 假能力 - realtime: 新建 internal/dto,快照三项契约对齐,/ws 只认会话身份 - httpapi: 流水幂等、standings 整表载荷、广播接线、强制改密、XFF 防伪造、records 写入、审计、赛事包内容、发布前备份、result.points、实例列表 - web: FormData 上传、YAML 解析、快照归一化、大屏解析层、bootstrap 降级、访客判定、主题切换 - console: 优雅关闭握手、中文化、免验证改管理员账号、选屏与日志轮转修复 - docs: 用户文档三份 + README - 主代理: 破纪录加分接线(levels 未加载导致恒 0)
234 lines
7.2 KiB
Go
234 lines
7.2 KiB
Go
package domain
|
||
|
||
import "testing"
|
||
|
||
func bonusConfig(mode RecordBonusMode) RecordBonusConfig {
|
||
return RecordBonusConfig{
|
||
Enabled: true, Mode: mode,
|
||
Levels: []RecordLevel{
|
||
{Code: "meet", Name: "赛会纪录", Points: 2},
|
||
{Code: "city", Name: "市级纪录", Points: 5},
|
||
{Code: "province", Name: "省级纪录", Points: 8},
|
||
},
|
||
}
|
||
}
|
||
|
||
func TestRecordBonusDisabled(t *testing.T) {
|
||
cfg := bonusConfig(RecordBonusMax)
|
||
cfg.Enabled = false
|
||
got := RecordBonusPoints(cfg, []RecordBreak{{EntryID: 1, Level: "meet", State: RecordBreakApproved}})
|
||
if got != nil {
|
||
t.Fatalf("未启用时 = %+v,期望 nil", got)
|
||
}
|
||
}
|
||
|
||
func TestRecordBonusStateFilter(t *testing.T) {
|
||
cases := []struct {
|
||
name string
|
||
state RecordBreakState
|
||
want float64
|
||
}{
|
||
{"已通过计入", RecordBreakApproved, 2},
|
||
{"待审核不计入", RecordBreakPending, 0},
|
||
{"已驳回不计入", RecordBreakRejected, 0},
|
||
{"状态空值不计入", "", 0},
|
||
}
|
||
for _, tc := range cases {
|
||
t.Run(tc.name, func(t *testing.T) {
|
||
got := RecordBonusPoints(bonusConfig(RecordBonusMax), []RecordBreak{{EntryID: 1, Level: "meet", State: tc.state}})
|
||
if tc.want == 0 {
|
||
if len(got) != 0 {
|
||
t.Fatalf("结果 = %+v,期望空", got)
|
||
}
|
||
return
|
||
}
|
||
if len(got) != 1 || !nearlyEqual(got[0].Points, tc.want) {
|
||
t.Fatalf("结果 = %+v,期望 %v 分", got, tc.want)
|
||
}
|
||
})
|
||
}
|
||
}
|
||
|
||
func TestRecordBonusUnknownLevel(t *testing.T) {
|
||
got := RecordBonusPoints(bonusConfig(RecordBonusMax), []RecordBreak{{EntryID: 1, Level: "galaxy", State: RecordBreakApproved}})
|
||
if len(got) != 0 {
|
||
t.Fatalf("未配置档位 = %+v,期望空(不能凭空加分)", got)
|
||
}
|
||
}
|
||
|
||
func TestRecordBonusModes(t *testing.T) {
|
||
breaks := []RecordBreak{
|
||
{ID: 1, EntryID: 1, Level: "meet", State: RecordBreakApproved},
|
||
{ID: 2, EntryID: 1, Level: "city", State: RecordBreakApproved},
|
||
{ID: 3, EntryID: 1, Level: "province", State: RecordBreakApproved},
|
||
}
|
||
cases := []struct {
|
||
name string
|
||
mode RecordBonusMode
|
||
want float64
|
||
}{
|
||
{"max 取最高档", RecordBonusMax, 8},
|
||
{"sum 累加", RecordBonusSum, 15},
|
||
{"模式空值按 max", "", 8},
|
||
}
|
||
for _, tc := range cases {
|
||
t.Run(tc.name, func(t *testing.T) {
|
||
got := RecordBonusPoints(bonusConfig(tc.mode), breaks)
|
||
if len(got) != 1 || !nearlyEqual(got[0].Points, tc.want) {
|
||
t.Fatalf("结果 = %+v,期望 %v", got, tc.want)
|
||
}
|
||
})
|
||
}
|
||
}
|
||
|
||
func TestRecordBonusGroupsByEntry(t *testing.T) {
|
||
breaks := []RecordBreak{
|
||
{ID: 1, EntryID: 2, Level: "meet", State: RecordBreakApproved},
|
||
{ID: 2, EntryID: 1, Level: "city", State: RecordBreakApproved},
|
||
{ID: 3, EntryID: 1, Level: "meet", State: RecordBreakApproved},
|
||
}
|
||
got := RecordBonusPoints(bonusConfig(RecordBonusMax), breaks)
|
||
if len(got) != 2 {
|
||
t.Fatalf("条数 = %d,期望 2(按报名项分组)", len(got))
|
||
}
|
||
if got[0].EntryID != 1 || got[1].EntryID != 2 {
|
||
t.Fatalf("顺序 = %+v,期望按 EntryID 升序", got)
|
||
}
|
||
if !nearlyEqual(got[0].Points, 5) || !nearlyEqual(got[1].Points, 2) {
|
||
t.Fatalf("分值 = %v/%v,期望 5/2", got[0].Points, got[1].Points)
|
||
}
|
||
}
|
||
|
||
func TestRecordBonusDedupLevel(t *testing.T) {
|
||
breaks := []RecordBreak{
|
||
{ID: 1, EntryID: 1, Level: "meet", State: RecordBreakApproved},
|
||
{ID: 2, EntryID: 1, Level: "meet", State: RecordBreakApproved},
|
||
}
|
||
got := RecordBonusPoints(bonusConfig(RecordBonusSum), breaks)
|
||
if len(got) != 1 || !nearlyEqual(got[0].Points, 2) {
|
||
t.Fatalf("重复档位 = %+v,期望只计一次 2 分", got)
|
||
}
|
||
if len(got[0].Levels) != 1 {
|
||
t.Fatalf("档位列表 = %v,期望去重", got[0].Levels)
|
||
}
|
||
}
|
||
|
||
func TestRecordBonusNoBreaks(t *testing.T) {
|
||
if got := RecordBonusPoints(bonusConfig(RecordBonusMax), nil); got != nil {
|
||
t.Fatalf("无破纪录登记 = %+v,期望 nil", got)
|
||
}
|
||
}
|
||
|
||
func TestRecordBonusLedger(t *testing.T) {
|
||
entries, results := fixture(lineup(2))
|
||
base := EventScoreInput{
|
||
Event: individualEvent(1), Category: testCategory(),
|
||
Channels: []ScoreChannel{athleteResultChannel(1)},
|
||
Profiles: map[int64]ScoringProfile{1: individualTop6()},
|
||
Entries: entries, Results: results,
|
||
RecordBreaks: []RecordBreak{{
|
||
ID: 1, EventID: 1, EntryID: 1, AthleteID: 101, TeamID: 1001,
|
||
Level: "meet", State: RecordBreakApproved,
|
||
}},
|
||
}
|
||
|
||
t.Run("未启用不产生加分流水", func(t *testing.T) {
|
||
cfg := bonusConfig(RecordBonusMax)
|
||
cfg.Enabled = false
|
||
base.RecordBonus = &cfg
|
||
es := mustScore(t, base)
|
||
for _, e := range es.Ledger {
|
||
if e.SourceType == SourceRecordBonus {
|
||
t.Fatalf("不应有加分流水: %+v", e)
|
||
}
|
||
}
|
||
})
|
||
|
||
t.Run("启用后归属到人", func(t *testing.T) {
|
||
cfg := bonusConfig(RecordBonusMax)
|
||
base.RecordBonus = &cfg
|
||
es := mustScore(t, base)
|
||
var found bool
|
||
for _, e := range es.Ledger {
|
||
if e.SourceType != SourceRecordBonus {
|
||
continue
|
||
}
|
||
found = true
|
||
if e.TeamID != 1001 || e.AthleteID != 101 || !nearlyEqual(e.Points, 2) {
|
||
t.Fatalf("加分流水 = %+v,期望 1001/101 记 2 分", e)
|
||
}
|
||
}
|
||
if !found {
|
||
t.Fatal("未产生加分流水")
|
||
}
|
||
})
|
||
|
||
t.Run("联队按份额拆分加分", func(t *testing.T) {
|
||
ps := []participant{{
|
||
entryID: 1, athleteID: 101, teamID: 1001, value: 1,
|
||
shares: []TeamShare{{TeamID: 1001, Weight: 3}, {TeamID: 1002, Weight: 2}},
|
||
}}
|
||
e2, r2 := fixture(ps)
|
||
cfg := bonusConfig(RecordBonusMax)
|
||
es := mustScore(t, EventScoreInput{
|
||
Event: individualEvent(1), Category: testCategory(),
|
||
Channels: []ScoreChannel{athleteResultChannel(1)},
|
||
Profiles: map[int64]ScoringProfile{1: individualTop6()},
|
||
Entries: e2, Results: r2,
|
||
RecordBonus: &cfg,
|
||
RecordBreaks: []RecordBreak{{
|
||
ID: 1, EventID: 1, EntryID: 1, AthleteID: 101, TeamID: 1001,
|
||
Level: "meet", State: RecordBreakApproved,
|
||
}},
|
||
Options: ScoreOptions{UnionAttribution: UnionSplit},
|
||
})
|
||
var bonus float64
|
||
for _, e := range es.Ledger {
|
||
if e.SourceType == SourceRecordBonus && e.TeamID == 1002 {
|
||
bonus += e.Points
|
||
}
|
||
}
|
||
if !nearlyEqual(bonus, 0.8) {
|
||
t.Fatalf("单位1002 的加分 = %v,期望 2 × 0.4 = 0.8", bonus)
|
||
}
|
||
})
|
||
}
|
||
|
||
// TestRecordBonusLedgerFiltersByCategory 验证按「小项+组别」过滤:
|
||
// 上层逐组别调用 ScoreEvent,别的组别的破纪录登记不能在本组别重复记一次加分。
|
||
func TestRecordBonusLedgerFiltersByCategory(t *testing.T) {
|
||
entries, results := fixture(lineup(2))
|
||
entries[1].CategoryID = 2 // entry2 属于另一个组别
|
||
cfg := bonusConfig(RecordBonusMax)
|
||
es := mustScore(t, 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},
|
||
{ID: 2, EventID: 1, EntryID: 2, AthleteID: 102, TeamID: 1002, Level: "meet", State: RecordBreakApproved},
|
||
},
|
||
})
|
||
|
||
var thisCategory, otherCategory int
|
||
for _, e := range es.Ledger {
|
||
if e.SourceType != SourceRecordBonus {
|
||
continue
|
||
}
|
||
switch e.EntryID {
|
||
case 1:
|
||
thisCategory++
|
||
case 2:
|
||
otherCategory++
|
||
}
|
||
}
|
||
if thisCategory != 1 {
|
||
t.Fatalf("本组别加分流水 = %d 条,期望 1", thisCategory)
|
||
}
|
||
if otherCategory != 0 {
|
||
t.Fatalf("别的组别的加分不应记入本组别,实际 %d 条", otherCategory)
|
||
}
|
||
}
|