From 9b6dba4c2bf070bf358292e86c2990ac7c1d019d Mon Sep 17 00:00:00 2001 From: wangjia <809946525@qq.com> Date: Fri, 19 Jun 2026 14:04:22 +0800 Subject: [PATCH] =?UTF-8?q?test(backend):=20=E8=A1=A5=20redeem=5Fcode=20?= =?UTF-8?q?=E5=8D=95=E6=B5=8B=20+=20gitignore=20todo/=20=E4=B8=8E=20gencod?= =?UTF-8?q?e=20=E4=BA=A7=E7=89=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 覆盖 NormalizeCode 边界/幂等、GenerateRedeemCode 格式/无歧义字母表 (剔除 0/O/1/I/L)/无碰撞。.gitignore 忽略全局 todo skill 本地数据 与 backend/gencode 构建产物。 Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01Y2Wdwo7SmgBJU37cBrkhPK --- .gitignore | 4 + backend/internal/util/redeem_code_test.go | 94 +++++++++++++++++++++++ 2 files changed, 98 insertions(+) create mode 100644 backend/internal/util/redeem_code_test.go diff --git a/.gitignore b/.gitignore index 219f753..24e78be 100644 --- a/.gitignore +++ b/.gitignore @@ -55,6 +55,10 @@ coverage/ # 编译产物(后端可执行文件) backend/issue +backend/gencode + +# 全局 todo skill 的本地数据(看板/任务,不入库) +/todo/ # Claude 私有文件 .claude/commands/ diff --git a/backend/internal/util/redeem_code_test.go b/backend/internal/util/redeem_code_test.go new file mode 100644 index 0000000..3b5fa0e --- /dev/null +++ b/backend/internal/util/redeem_code_test.go @@ -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) + } +}