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:
@@ -55,6 +55,10 @@ coverage/
|
||||
|
||||
# 编译产物(后端可执行文件)
|
||||
backend/issue
|
||||
backend/gencode
|
||||
|
||||
# 全局 todo skill 的本地数据(看板/任务,不入库)
|
||||
/todo/
|
||||
|
||||
# Claude 私有文件
|
||||
.claude/commands/
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user