Files
app-261004/scripts/test-validation.mjs
T
2026-10-08 11:09:37 +08:00

128 lines
4.3 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 校验规则的单元自测(不依赖服务器与数据库)。
*
* 重点验证容易出错的地方:
* - 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;