From f89cd998ef7e44a01a3a37b393a8848d33cba533 Mon Sep 17 00:00:00 2001 From: lovife <474239041@qq.com> Date: Wed, 30 Sep 2026 12:38:48 +0800 Subject: [PATCH] =?UTF-8?q?test(unit):=20=E4=B8=89=E4=B8=AA=E7=BA=AF?= =?UTF-8?q?=E5=87=BD=E6=95=B0=E7=9A=84=E6=9C=80=E5=B0=8F=E5=8D=95=E6=B5=8B?= =?UTF-8?q?=EF=BC=8812=20=E6=9D=A1=EF=BC=89=E2=80=94=E2=80=94=20=E6=9C=AC?= =?UTF-8?q?=E4=BB=93=E5=BA=93=E7=AC=AC=E4=B8=80=E6=89=B9=E5=8D=95=E6=B5=8B?= =?UTF-8?q?=EF=BC=88F-9=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 后端验证一直靠 E2E(打真实接口,很硬但要起服务连库)。这批先给 「输入输出确定、没有 IO」的纯函数补最小单测,成本最低、回归收益最高; 其余继续靠 E2E,不为了覆盖率硬凑。 · IdentityNormalizerTest:姓名去所有空白但**保留间隔号**(少数民族姓名的 「·」是真实的,顺手清掉等于改人名字)、全角转半角、证件号小写 x 收尾统一、 null 不抛 NPE · FileProcessServiceTest:扩展名必须自带点号(少一个字符不报错,只会产出 「身份证jpg」这种客户双击打不开的文件 —— F-46)、空变量用「未填」占位 (留空会出现「张--身份证.jpg」)、文件名里的 / 必须清掉(「学籍表(1/2)」 带 / 根本写不进磁盘)、超长截到 90 且不吃掉扩展名、sanitize 收拢连续分隔符 每条断言都对应一个真实踩过的坑 —— 断言的是「有理由的规则」,不是覆盖率。 验证:mvn test → 12/12 通过(exit 0) --- .../file/service/FileProcessServiceTest.java | 88 +++++++++++++++++++ .../fill/support/IdentityNormalizerTest.java | 51 +++++++++++ 2 files changed, 139 insertions(+) create mode 100644 backend/xt-server/src/test/java/cn/tianbaotong/fill/module/file/service/FileProcessServiceTest.java create mode 100644 backend/xt-server/src/test/java/cn/tianbaotong/fill/support/IdentityNormalizerTest.java diff --git a/backend/xt-server/src/test/java/cn/tianbaotong/fill/module/file/service/FileProcessServiceTest.java b/backend/xt-server/src/test/java/cn/tianbaotong/fill/module/file/service/FileProcessServiceTest.java new file mode 100644 index 0000000..7c0f61e --- /dev/null +++ b/backend/xt-server/src/test/java/cn/tianbaotong/fill/module/file/service/FileProcessServiceTest.java @@ -0,0 +1,88 @@ +package cn.tianbaotong.fill.module.file.service; + +import org.junit.jupiter.api.Test; + +import java.util.HashMap; +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * 规范文件名引擎的最小单测(《08》F-9 / F-46)。 + * + *

断言的是有理由的规则,不是覆盖率: + *

+ */ +class FileProcessServiceTest { + + private final FileProcessService svc = new FileProcessService(); + + private static Map vars(String name, String id, String material) { + Map m = new HashMap<>(); + m.put("姓名", name); + m.put("证件号", id); + m.put("材料名", material); + return m; + } + + @Test + void 默认模板_姓名证件号材料名_扩展名带点() { + String n = svc.renderName(null, vars("张三", "361029200907295110", "身份证"), ".jpg"); + assertEquals("张三-361029200907295110-身份证.jpg", n); + } + + @Test + void 模板带后缀占位符时不重复拼扩展名() { + String n = svc.renderName("{材料名}-{姓名}{后缀}", + vars("张三", "361029200907295110", "身份证"), ".jpg"); + assertEquals("身份证-张三.jpg", n); + } + + @Test + void 空变量用未填占位_不会出现双横线() { + String n = svc.renderName(null, vars("张三", "", "身份证"), ".jpg"); + assertEquals("张三-未填-身份证.jpg", n); + assertFalse(n.contains("--"), "留空会出现「张--身份证.jpg」,看起来像系统出错: " + n); + } + + @Test + void 文件名里的斜杠必须被清掉() { + // 「学籍表(1/2)」在真实学生的上传里很常见;带 / 的名字写不进磁盘 + String n = svc.renderName(null, vars("张三", "361029200907295110", "学籍表(1/2)"), ".jpg"); + assertFalse(n.contains("/"), "斜杠会导致写盘失败: " + n); + assertTrue(n.contains("学籍表(1_2)")); + } + + @Test + void 超长文件名截到安全长度() { + Map m = vars("张".repeat(120), "361029200907295110", "身份证"); + String n = svc.renderName(null, m, ".jpg"); + assertTrue(n.length() <= 90, "Windows 路径 260 限制,正文要给目录留空间: " + n.length()); + assertTrue(n.endsWith(".jpg"), "截断不能吃掉扩展名"); + } + + @Test + void sanitize_空白给未命名_全非法字符收拢成单个下划线() { + // "///" → "___" → 收拢成 "_"(不是「未命名」:未命名只留给纯空白, + // 全非法字符收拢后仍是可写盘的合法名字) + assertEquals("_", FileProcessService.sanitize("///")); + assertEquals("未命名", FileProcessService.sanitize(" ")); + assertEquals(null, FileProcessService.sanitize(null)); + } + + @Test + void sanitize_连续分隔符收拢() { + assertEquals("a_b", FileProcessService.sanitize("a___b")); + assertEquals("a-b", FileProcessService.sanitize("a---b")); + } +} diff --git a/backend/xt-server/src/test/java/cn/tianbaotong/fill/support/IdentityNormalizerTest.java b/backend/xt-server/src/test/java/cn/tianbaotong/fill/support/IdentityNormalizerTest.java new file mode 100644 index 0000000..2fbc550 --- /dev/null +++ b/backend/xt-server/src/test/java/cn/tianbaotong/fill/support/IdentityNormalizerTest.java @@ -0,0 +1,51 @@ +package cn.tianbaotong.fill.support; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +/** + * {@link IdentityNormalizer} 的最小单测(《08》F-9:先给纯函数补单测,其余继续靠 E2E)。 + * + *

选这三条规则断言,是因为每一条背后都有一个真实踩过的坑: + *

    + *
  • 姓名去空白、但不去间隔号 —— 少数民族姓名里的「·」是真实的; + * 顺手"清一清"会弄坏一部分人的名字(F-20 的另一面:规范化规则不能互相借用);
  • + *
  • 全角转半角 —— 手机输入法常给全角数字,而它进校验位算法就是错的;
  • + *
  • 证件号小写 {@code x} 收尾要统一成大写 —— 名册比对按字符串等值。
  • + *
+ */ +class IdentityNormalizerTest { + + @Test + void 姓名去所有空白_含全角空格() { + assertEquals("张三", IdentityNormalizer.name("张 三")); + assertEquals("张三", IdentityNormalizer.name("张 三")); // 全角空格 U+3000 + assertEquals("张三", IdentityNormalizer.name(" 张\t三\n")); + } + + @Test + void 姓名里的间隔号必须保留() { + // 阿依古丽·买买提:间隔号是姓名的一部分,清掉等于改了人家的名字 + assertEquals("阿依古丽·买买提", IdentityNormalizer.name("阿依古丽·买买提")); + assertEquals("阿依古丽·买买提", IdentityNormalizer.name("阿依古丽 · 买买提")); + } + + @Test + void 全角ASCII转半角_其余不动() { + assertEquals("123X", IdentityNormalizer.toHalfWidth("123X")); + assertEquals("中文不变", IdentityNormalizer.toHalfWidth("中文不变")); + assertEquals("", IdentityNormalizer.toHalfWidth(null)); + } + + @Test + void 证件号去空白_转大写_全角可入() { + assertEquals("36102920090729511X", IdentityNormalizer.idCard("3610292009 0729511x")); + assertEquals("36102920090729511X", IdentityNormalizer.idCard("36102920090729511X")); + } + + @Test + void 姓名空值返回空串而不是NPE() { + assertEquals("", IdentityNormalizer.name(null)); + } +}