Files
Scoreboard/server/internal/domain/records_test.go
T
AskaEth 7d5d94f815 feat: M0-M2 骨架落地(服务端/前端/宿主/数据库/积分引擎)
- server: Go 单二进制骨架,embed 内嵌前端,配置/迁移/HTTP 路由
- store: system.db + event.db 两套迁移,业务表 + 种子数据
- domain: 积分引擎(并列三口径/计分通道/流水/排名/破纪录)+ 68 个测试
- web: Vite + Vue 三入口骨架 + shared 层(类型/API/WebSocket 客户端)
- console: Electron 宿主骨架(选屏/防熄屏/进程守护/托盘)
2026-09-09 18:36:40 +08:00

196 lines
5.9 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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)
}
})
}