diff --git a/server/utils/post-comment-guest.test.ts b/server/utils/post-comment-guest.test.ts new file mode 100644 index 0000000..df45853 --- /dev/null +++ b/server/utils/post-comment-guest.test.ts @@ -0,0 +1,38 @@ +import { describe, expect, test } from "bun:test"; +import { + GuestCommentValidationError, + normalizeGuestDisplayName, + validateGuestCommentBody, +} from "./post-comment-guest"; + +describe("normalizeGuestDisplayName", () => { + test("trims and accepts valid name", () => { + expect(normalizeGuestDisplayName(" ada ")).toBe("ada"); + }); + + test("rejects empty", () => { + expect(() => normalizeGuestDisplayName(" ")).toThrow(GuestCommentValidationError); + }); + + test("rejects too long", () => { + expect(() => normalizeGuestDisplayName("a".repeat(33))).toThrow(GuestCommentValidationError); + }); +}); + +describe("validateGuestCommentBody", () => { + test("accepts plain text", () => { + expect(validateGuestCommentBody("你好,世界")).toBe("你好,世界"); + }); + + test("rejects http URL", () => { + expect(() => validateGuestCommentBody("see http://x.com")).toThrow(GuestCommentValidationError); + }); + + test("rejects markdown link", () => { + expect(() => validateGuestCommentBody("[a](http://b)")).toThrow(GuestCommentValidationError); + }); + + test("rejects too long", () => { + expect(() => validateGuestCommentBody("z".repeat(501))).toThrow(GuestCommentValidationError); + }); +}); diff --git a/server/utils/post-comment-guest.ts b/server/utils/post-comment-guest.ts new file mode 100644 index 0000000..aaebd1f --- /dev/null +++ b/server/utils/post-comment-guest.ts @@ -0,0 +1,38 @@ +const MAX_NAME = 32; +const MAX_BODY = 500; + +export class GuestCommentValidationError extends Error { + constructor(message: string) { + super(message); + this.name = "GuestCommentValidationError"; + } +} + +export function normalizeGuestDisplayName(raw: string): string { + const t = raw.trim(); + if (!t) { + throw new GuestCommentValidationError("请填写昵称"); + } + if (t.length > MAX_NAME) { + throw new GuestCommentValidationError("昵称过长"); + } + return t; +} + +/** 返回 trim 后正文;违规抛 GuestCommentValidationError(由 API 映射为 400) */ +export function validateGuestCommentBody(raw: string): string { + const t = raw.trim(); + if (!t) { + throw new GuestCommentValidationError("请填写内容"); + } + if (t.length > MAX_BODY) { + throw new GuestCommentValidationError("内容过长"); + } + if (/https?:\/\//i.test(t) || /\bwww\./i.test(t)) { + throw new GuestCommentValidationError("不能包含链接"); + } + if (/\[[^\]]*\]\([^)]*\)/.test(t)) { + throw new GuestCommentValidationError("不能使用 Markdown 链接"); + } + return t; +}