package export import ( "bytes" "path/filepath" "testing" "github.com/xuri/excelize/v2" ) func TestExportStandingsXLSX(t *testing.T) { db := openEventDB(t) seedEvent(t, db) dst := filepath.Join(t.TempDir(), "总表.xlsx") if err := ExportStandingsXLSX(db, dst, Options{}); err != nil { t.Fatalf("导出总表: %v", err) } f, err := excelize.OpenFile(dst) if err != nil { t.Fatalf("重新打开 xlsx: %v", err) } defer func() { _ = f.Close() }() want := []string{"总积分榜", "团体奖牌榜", "个人积分奖牌榜"} if got := f.GetSheetList(); !equalStrings(got, want) { t.Fatalf("sheet 列表 = %v,期望 %v", got, want) } // 版式固定:1 标题 / 2 副标题 / 3 空行 / 4 表头 / 5 起数据;2 个单位 → 6 行。 team, err := f.GetRows("总积分榜") if err != nil { t.Fatalf("读取总积分榜: %v", err) } if len(team) != 6 { t.Fatalf("总积分榜行数 = %d,期望 6", len(team)) } if got := cell(team, 0, 0); got != "测试运动会 · 总积分榜" { t.Errorf("标题 = %q", got) } if got := cell(team, 3, 0); got != "名次" { t.Errorf("表头首列 = %q,期望 名次", got) } if got := cell(team, 4, 1); got != "T1" { t.Errorf("第一名单位代码 = %q,期望 T1", got) } if got := cell(team, 4, 3); got != "10" { t.Errorf("T1 总分 = %q,期望 10", got) } if got := cell(team, 4, 6); got != "1" { t.Errorf("T1 金牌 = %q,期望 1", got) } if got := cell(team, 5, 1); got != "T2" { t.Errorf("第二名单位代码 = %q,期望 T2", got) } medal, err := f.GetRows("团体奖牌榜") if err != nil { t.Fatalf("读取团体奖牌榜: %v", err) } if len(medal) != 6 { t.Fatalf("团体奖牌榜行数 = %d,期望 6", len(medal)) } if got := cell(medal, 4, 3); got != "1" { // T1 金牌 t.Errorf("T1 奖牌榜金牌 = %q,期望 1", got) } athlete, err := f.GetRows("个人积分奖牌榜") if err != nil { t.Fatalf("读取个人积分奖牌榜: %v", err) } if len(athlete) != 6 { t.Fatalf("个人积分奖牌榜行数 = %d,期望 6", len(athlete)) } if got := cell(athlete, 4, 1); got != "张三" { t.Errorf("个人榜第一行 = %q,期望 张三", got) } } func TestWriteStandingsXLSXStream(t *testing.T) { db := openEventDB(t) seedEvent(t, db) wb, err := LoadStandingsWorkbook(db, Options{}) if err != nil { t.Fatalf("读取总表数据: %v", err) } var buf bytes.Buffer if err := WriteStandingsXLSX(&buf, wb); err != nil { t.Fatalf("流式导出: %v", err) } f, err := excelize.OpenReader(bytes.NewReader(buf.Bytes())) if err != nil { t.Fatalf("打开流式导出的 xlsx: %v", err) } defer func() { _ = f.Close() }() if got := len(f.GetSheetList()); got != 3 { t.Errorf("sheet 数 = %d,期望 3", got) } } func TestExportEventXLSX(t *testing.T) { db := openEventDB(t) seedEvent(t, db) dst := filepath.Join(t.TempDir(), "分项.xlsx") if err := ExportEventXLSX(db, 1, dst, Options{}); err != nil { t.Fatalf("导出分项成绩表: %v", err) } f, err := excelize.OpenFile(dst) if err != nil { t.Fatalf("重新打开 xlsx: %v", err) } defer func() { _ = f.Close() }() want := []string{"田径·100米跑·男子"} if got := f.GetSheetList(); !equalStrings(got, want) { t.Fatalf("sheet 列表 = %v,期望 %v", got, want) } rows, err := f.GetRows(want[0]) if err != nil { t.Fatalf("读取分项成绩: %v", err) } if len(rows) != 6 { t.Fatalf("分项成绩行数 = %d,期望 6", len(rows)) } if got := cell(rows, 4, 0); got != "1" { t.Errorf("第一名名次 = %q,期望 1", got) } if got := cell(rows, 4, 1); got != "101" { t.Errorf("第一名号码 = %q,期望 101", got) } if got := cell(rows, 4, 3); got != "张三" { t.Errorf("第一名运动员 = %q,期望 张三", got) } if got := cell(rows, 4, 4); got != "11.58 秒" { t.Errorf("第一名成绩 = %q,期望 11.58 秒", got) } if got := cell(rows, 4, 5); got != "10" { t.Errorf("第一名积分 = %q,期望 10", got) } if got := cell(rows, 4, 6); got != "有效" { t.Errorf("第一名状态 = %q,期望 有效", got) } } func TestExportAllXLSX(t *testing.T) { db := openEventDB(t) seedEvent(t, db) dst := filepath.Join(t.TempDir(), "全部.xlsx") if err := ExportAllXLSX(db, dst, Options{}); err != nil { t.Fatalf("导出全部: %v", err) } f, err := excelize.OpenFile(dst) if err != nil { t.Fatalf("重新打开 xlsx: %v", err) } defer func() { _ = f.Close() }() want := []string{"总积分榜", "团体奖牌榜", "个人积分奖牌榜", "田径·100米跑·男子"} if got := f.GetSheetList(); !equalStrings(got, want) { t.Fatalf("sheet 列表 = %v,期望 %v", got, want) } } func TestExportTeamDetailXLSX(t *testing.T) { db := openEventDB(t) seedEvent(t, db) dst := filepath.Join(t.TempDir(), "明细.xlsx") if err := ExportTeamDetailXLSX(db, 1, dst, Options{}); err != nil { t.Fatalf("导出单位积分明细: %v", err) } f, err := excelize.OpenFile(dst) if err != nil { t.Fatalf("重新打开 xlsx: %v", err) } defer func() { _ = f.Close() }() sheet := "第一分公司积分明细" rows, err := f.GetRows(sheet) if err != nil { t.Fatalf("读取 %s: %v", sheet, err) } // 4 行版式 + 1 条明细 + 1 行合计 if len(rows) != 6 { t.Fatalf("%s 行数 = %d,期望 6", sheet, len(rows)) } if got := cell(rows, 4, 1); got != "田径" { t.Errorf("明细大项 = %q,期望 田径", got) } if got := cell(rows, 4, 5); got != "1" { t.Errorf("明细名次 = %q,期望 1", got) } if got := cell(rows, 4, 7); got != "成绩" { t.Errorf("明细来源 = %q,期望 成绩", got) } if got := cell(rows, 4, 8); got != "10" { t.Errorf("明细积分 = %q,期望 10", got) } if got := cell(rows, 5, 0); got != "合计" { t.Errorf("合计行首列 = %q,期望 合计", got) } if got := cell(rows, 5, 8); got != "10" { t.Errorf("合计积分 = %q,期望 10", got) } } // TestLoadStandingsHonoursSettings 验证个人分折算系数确实来自赛事库设置。 func TestLoadStandingsHonoursSettings(t *testing.T) { db := openEventDB(t) seedEvent(t, db) if _, err := db.Exec(`UPDATE settings SET value = '2' WHERE key = 'standings.individual_to_team_weight'`); err != nil { t.Fatalf("修改折算系数: %v", err) } wb, err := LoadStandingsWorkbook(db, Options{}) if err != nil { t.Fatalf("读取总表数据: %v", err) } if len(wb.Team) != 2 { t.Fatalf("单位数 = %d,期望 2", len(wb.Team)) } if wb.Team[0].Points != 20 { t.Errorf("系数 2 时 T1 总分 = %v,期望 20", wb.Team[0].Points) } } func TestLoadEventWorkbookMissingEvent(t *testing.T) { db := openEventDB(t) seedEvent(t, db) if _, err := LoadEventWorkbook(db, 999, Options{}); err == nil { t.Fatal("导出不存在的小项应报错") } } func cell(rows [][]string, r, c int) string { if r < 0 || r >= len(rows) { return "" } if c < 0 || c >= len(rows[r]) { return "" } return rows[r][c] } func equalStrings(a, b []string) bool { if len(a) != len(b) { return false } for i := range a { if a[i] != b[i] { return false } } return true }