This commit is contained in:
1 parent
c1541b80df
commit
b4c7908029
97 files changed
+12403
-154
No files matched your search
@@ -0,0 +1,127 @@
|
||||
/**
|
||||
* 校验规则的单元自测(不依赖服务器与数据库)。
|
||||
*
|
||||
* 重点验证容易出错的地方:
|
||||
* - PATCH 空对象必须被拒绝(`.partial()` 不会移除 `.default()`)
|
||||
* - slug 格式、标签去重、日期转换
|
||||
* - 列表查询参数的类型强制与边界
|
||||
*
|
||||
* 用法:node scripts/test-validation.mjs
|
||||
*/
|
||||
|
||||
import {
|
||||
createPostSchema,
|
||||
updatePostSchema,
|
||||
listPostsQuerySchema,
|
||||
estimateReadingMinutes,
|
||||
} from "../lib/validation/post.ts";
|
||||
|
||||
let pass = 0;
|
||||
let fail = 0;
|
||||
|
||||
function check(name, condition, detail = "") {
|
||||
if (condition) {
|
||||
pass++;
|
||||
console.log(` [PASS] ${name}`);
|
||||
} else {
|
||||
fail++;
|
||||
console.log(` [FAIL] ${name} ${detail}`);
|
||||
}
|
||||
}
|
||||
|
||||
console.log("── createPostSchema ─────────────────");
|
||||
const validCreate = createPostSchema.safeParse({
|
||||
slug: "hello-world",
|
||||
title: "标题",
|
||||
summary: "摘要",
|
||||
content: "正文",
|
||||
});
|
||||
check("合法输入通过", validCreate.success);
|
||||
check("status 默认为 draft", validCreate.data?.status === "draft");
|
||||
check("tags 默认为空数组", Array.isArray(validCreate.data?.tags) && validCreate.data.tags.length === 0);
|
||||
|
||||
const badSlug = createPostSchema.safeParse({
|
||||
slug: "Hello_World",
|
||||
title: "t",
|
||||
summary: "s",
|
||||
content: "c",
|
||||
});
|
||||
check("非法 slug 被拒绝", !badSlug.success);
|
||||
|
||||
const emptyTitle = createPostSchema.safeParse({
|
||||
slug: "ok",
|
||||
title: " ",
|
||||
summary: "s",
|
||||
content: "c",
|
||||
});
|
||||
check("纯空白标题被拒绝", !emptyTitle.success);
|
||||
|
||||
const dupTags = createPostSchema.safeParse({
|
||||
slug: "ok",
|
||||
title: "t",
|
||||
summary: "s",
|
||||
content: "c",
|
||||
tags: ["a", "a", "b"],
|
||||
});
|
||||
check("标签自动去重", dupTags.success && dupTags.data.tags.length === 2, JSON.stringify(dupTags.data?.tags));
|
||||
|
||||
console.log("\n── updatePostSchema(关键回归点)──");
|
||||
const emptyUpdate = updatePostSchema.safeParse({});
|
||||
check("空对象被拒绝(回归:不得因 default 而通过)", !emptyUpdate.success, JSON.stringify(emptyUpdate.data));
|
||||
|
||||
const titleOnly = updatePostSchema.safeParse({ title: "新标题" });
|
||||
check("只传 title 通过", titleOnly.success);
|
||||
check("未传的字段不出现在结果中", !("summary" in (titleOnly.data ?? {})), JSON.stringify(titleOnly.data));
|
||||
|
||||
const tagsOnly = updatePostSchema.safeParse({ tags: ["x"] });
|
||||
check("只传 tags 通过", tagsOnly.success);
|
||||
|
||||
const invalidStatus = updatePostSchema.safeParse({ status: "archived" });
|
||||
check("非法 status 被拒绝", !invalidStatus.success);
|
||||
|
||||
const slugUpdate = updatePostSchema.safeParse({ slug: "new-slug" });
|
||||
check("可单独更新 slug", slugUpdate.success);
|
||||
|
||||
console.log("\n── listPostsQuerySchema ─────────────");
|
||||
const defaults = listPostsQuerySchema.safeParse({});
|
||||
check("默认 limit=20", defaults.data?.limit === 20);
|
||||
check("默认 offset=0", defaults.data?.offset === 0);
|
||||
check("默认 includeDrafts=false", defaults.data?.includeDrafts === false);
|
||||
|
||||
const coerced = listPostsQuerySchema.safeParse({ limit: "5", offset: "10" });
|
||||
check("字符串数字被强制转换", coerced.data?.limit === 5 && coerced.data?.offset === 10);
|
||||
|
||||
const tooBig = listPostsQuerySchema.safeParse({ limit: "101" });
|
||||
check("limit 超上限被拒绝", !tooBig.success);
|
||||
|
||||
const zero = listPostsQuerySchema.safeParse({ limit: "0" });
|
||||
check("limit=0 被拒绝", !zero.success);
|
||||
|
||||
const negative = listPostsQuerySchema.safeParse({ offset: "-5" });
|
||||
check("负 offset 被拒绝", !negative.success);
|
||||
|
||||
const drafts = listPostsQuerySchema.safeParse({ includeDrafts: "true" });
|
||||
check("includeDrafts 字符串转布尔", drafts.data?.includeDrafts === true);
|
||||
|
||||
const badStatus = listPostsQuerySchema.safeParse({ status: "nope" });
|
||||
check("非法 status 被拒绝", !badStatus.success);
|
||||
|
||||
console.log("\n── estimateReadingMinutes ───────────");
|
||||
check("空内容至少 1 分钟", estimateReadingMinutes("") === 1);
|
||||
check("短内容为 1 分钟", estimateReadingMinutes("hello world") === 1);
|
||||
check(
|
||||
"长中文内容时长增加",
|
||||
estimateReadingMinutes("中".repeat(1400)) >= 3,
|
||||
`got ${estimateReadingMinutes("中".repeat(1400))}`,
|
||||
);
|
||||
check(
|
||||
"长英文内容时长增加",
|
||||
estimateReadingMinutes("word ".repeat(1000)) >= 4,
|
||||
`got ${estimateReadingMinutes("word ".repeat(1000))}`,
|
||||
);
|
||||
|
||||
console.log("\n================================");
|
||||
console.log(` 通过 ${pass} / ${pass + fail}`);
|
||||
console.log("================================");
|
||||
|
||||
process.exitCode = fail > 0 ? 1 : 0;
|
||||
Reference in new issue
Block a user