test(backend): 补 redeem_code 单测 + gitignore todo/ 与 gencode 产物

覆盖 NormalizeCode 边界/幂等、GenerateRedeemCode 格式/无歧义字母表
(剔除 0/O/1/I/L)/无碰撞。.gitignore 忽略全局 todo skill 本地数据
与 backend/gencode 构建产物。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Y2Wdwo7SmgBJU37cBrkhPK
This commit is contained in:
wangjia
2026-06-19 14:04:22 +08:00
parent 54451f0e52
commit 9b6dba4c2b
2 changed files with 98 additions and 0 deletions
+4
View File
@@ -55,6 +55,10 @@ coverage/
# 编译产物(后端可执行文件)
backend/issue
backend/gencode
# 全局 todo skill 的本地数据(看板/任务,不入库)
/todo/
# Claude 私有文件
.claude/commands/
+94
View File
@@ -0,0 +1,94 @@
package util
import (
"strings"
"testing"
)
func TestNormalizeCode(t *testing.T) {
cases := []struct {
name string
in string
want string
}{
{"展示格式带连字符", "JIUKU-YD9S-7CAP", "JIUKUYD9S7CAP"},
{"小写转大写", "jiuku-yd9s-7cap", "JIUKUYD9S7CAP"},
{"去除空格", "JIUKU YD9S 7CAP", "JIUKUYD9S7CAP"},
{"去除制表/换行/回车", "JIUKU\tYD9S\n7CAP\r", "JIUKUYD9S7CAP"},
{"已归一化幂等", "JIUKUYD9S7CAP", "JIUKUYD9S7CAP"},
{"空串", "", ""},
{"混合大小写与分隔", " jiUKu--yd9s 7caP\n", "JIUKUYD9S7CAP"},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
if got := NormalizeCode(c.in); got != c.want {
t.Fatalf("NormalizeCode(%q) = %q, want %q", c.in, got, c.want)
}
})
}
}
func TestNormalizeCodeIdempotent(t *testing.T) {
// 二次归一化结果不变
once := NormalizeCode("jiuku-yd9s-7cap")
if twice := NormalizeCode(once); twice != once {
t.Fatalf("NormalizeCode 非幂等: %q -> %q", once, twice)
}
}
func TestGenerateRedeemCodeFormat(t *testing.T) {
for i := 0; i < 200; i++ {
code := GenerateRedeemCode()
// 展示格式:JIUKU-XXXX-XXXX
parts := strings.Split(code, "-")
if len(parts) != 3 {
t.Fatalf("码段数应为 3,得到 %d%q", len(parts), code)
}
if parts[0] != "JIUKU" {
t.Fatalf("前缀应为 JIUKU,得到 %q%q", parts[0], code)
}
if len(parts[1]) != 4 || len(parts[2]) != 4 {
t.Fatalf("随机段长度应各为 4,得到 %d/%d%q", len(parts[1]), len(parts[2]), code)
}
// 归一化后 = 前缀 + 8 位随机
norm := NormalizeCode(code)
if len(norm) != len("JIUKU")+8 {
t.Fatalf("归一化长度应为 %d,得到 %d%q", len("JIUKU")+8, len(norm), norm)
}
// 随机段只含无歧义字母表字符(不含 0/O/1/I/L)
random := parts[1] + parts[2]
for _, r := range random {
if !strings.ContainsRune(codeAlphabet, r) {
t.Fatalf("出现字母表外字符 %q%q", r, code)
}
}
}
}
func TestCodeAlphabetExcludesAmbiguous(t *testing.T) {
for _, bad := range []rune{'0', 'O', '1', 'I', 'L'} {
if strings.ContainsRune(codeAlphabet, bad) {
t.Fatalf("字母表不应包含易混字符 %q", bad)
}
}
}
func TestGenerateRedeemCodeUnique(t *testing.T) {
// 不要求绝对唯一,但大量生成应几乎无碰撞(31^8 空间)
const n = 1000
seen := make(map[string]struct{}, n)
dup := 0
for i := 0; i < n; i++ {
c := NormalizeCode(GenerateRedeemCode())
if _, ok := seen[c]; ok {
dup++
}
seen[c] = struct{}{}
}
if dup > 0 {
t.Fatalf("%d 次生成出现 %d 次重复,随机性可疑", n, dup)
}
}