80 lines
3.4 KiB
TypeScript
80 lines
3.4 KiB
TypeScript
/* 邮件模板自检:验证渲染不炸、转义生效、关键字段都在。
|
||||
|
|
* 跑法(见 package.json selftest 同款套路,tools/ 不在 src 的 tsc 范围里):
|
|||
|
|
* npx tsc tools/mailtest.ts --outDir .selftest-build --module commonjs \
|
|||
|
|
* --target es2022 --moduleResolution node --skipLibCheck --esModuleInterop
|
|||
|
|
* node .selftest-build/tools/mailtest.js
|
|||
|
|
*/
|
|||
|
|
import { replyMailHtml, replyMailSubject, adminMailHtml, adminMailSubject } from '../src/lib/mail';
|
|||
|
|
import type { CommentRow } from '../src/types';
|
|||
|
|
|
|||
|
|
let pass = 0,
|
|||
|
|
fail = 0;
|
|||
|
|
const ok = (name: string, cond: boolean) => {
|
|||
|
|
if (cond) {
|
|||
|
|
pass++;
|
|||
|
|
console.log(' ✓', name);
|
|||
|
|
} else {
|
|||
|
|
fail++;
|
|||
|
|
console.log(' ✗', name);
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
// CommentRow 只要模板用到的字段;其余给空值占位
|
|||
|
|
const row = (id: number, content: string, createdAt: string): CommentRow =>
|
|||
|
|
({
|
|||
|
|
id,
|
|||
|
|
content,
|
|||
|
|
created_at: createdAt,
|
|||
|
|
page_key: '/20240811.html',
|
|||
|
|
site_name: '优世界',
|
|||
|
|
}) as unknown as CommentRow;
|
|||
|
|
|
|||
|
|
console.log('== 回复提醒模板(reply.html 气泡风) ==');
|
|||
|
|
const rInput = {
|
|||
|
|
siteName: '优世界',
|
|||
|
|
siteUrl: 'https://usj.cc',
|
|||
|
|
pageTitle: '迁移博客至hugo',
|
|||
|
|
pageUrl: 'https://usj.cc/20240811.html',
|
|||
|
|
parentComment: row(15580, '原始评论 <b>加粗</b>', '2026-10-01 10:00:00'),
|
|||
|
|
parentNick: '小赵',
|
|||
|
|
parentEmail: 'imql@qq.com',
|
|||
|
|
comment: row(15590, '试试 <script>alert(1)</script> 和 <img src=x onerror=alert(1)>', '2026-10-02 10:00:00'),
|
|||
|
|
commenterNick: '路人甲',
|
|||
|
|
commenterEmail: 'walker@example.com',
|
|||
|
|
toNick: '小赵',
|
|||
|
|
};
|
|||
|
|
const r = replyMailHtml(rInput);
|
|||
|
|
ok('渲染出 HTML', r.startsWith('<!DOCTYPE html>'));
|
|||
|
|
ok('包含站点名', r.includes('优世界'));
|
|||
|
|
ok('包含页面标题', r.includes('迁移博客至hugo'));
|
|||
|
|
ok('跳转链接带 atk_comment 锚参数', r.includes('atk_comment=15590'));
|
|||
|
|
ok('script 标签被转义', !r.includes('<script>alert(1)</script>'));
|
|||
|
|
ok('没有可执行的 onerror(img 白名单外的 img 一律转义成文本)', !/<img[^>]*onerror/i.test(r));
|
|||
|
|
ok('被回复者昵称出现', r.includes('小赵'));
|
|||
|
|
ok('回复者昵称出现', r.includes('路人甲'));
|
|||
|
|
ok('两个头像 MD5 都渲染', (r.match(/weavatar\.com\/avatar\//g) || []).length >= 2);
|
|||
|
|
ok('site_url 出现在落款', r.includes('https://usj.cc'));
|
|||
|
|
ok('主题格式正确', replyMailSubject(rInput) === '[优世界] 您收到了来自 @路人甲 的回信');
|
|||
|
|
|
|||
|
|
console.log('== 管理员通知模板(notice.html 气泡风) ==');
|
|||
|
|
const aInput = {
|
|||
|
|
siteName: '优世界',
|
|||
|
|
siteUrl: 'https://usj.cc',
|
|||
|
|
pageTitle: '留言',
|
|||
|
|
pageUrl: 'https://usj.cc/comment.html',
|
|||
|
|
comment: row(999, '你好,<img src="https://usj.cc/emotion/douyin/19.png" atk-emoticon="19"> 哈哈', '2026-10-02 11:00:00'),
|
|||
|
|
commenterNick: '路人',
|
|||
|
|
commenterEmail: 'x@example.com',
|
|||
|
|
adminNick: '博主',
|
|||
|
|
};
|
|||
|
|
const a = adminMailHtml(aInput);
|
|||
|
|
ok('渲染出 HTML', a.startsWith('<!DOCTYPE html>'));
|
|||
|
|
ok('标题是新评论', a.includes('有了新的评论'));
|
|||
|
|
ok('含作者昵称', a.includes('路人'));
|
|||
|
|
ok('表情包 img 被保留并内联宽度', /<img[^>]*width:100px[^>]*atk-emoticon/.test(a));
|
|||
|
|
ok('跳转链接带锚参数', a.includes('atk_comment=999'));
|
|||
|
|
ok('site_url 出现在落款', a.includes('https://usj.cc'));
|
|||
|
|
ok('主题格式正确', adminMailSubject(aInput) === '[优世界] 其中 "留言" 页面有一条新的评论!');
|
|||
|
|
|
|||
|
|
console.log('\n通过 ' + pass + ' / 失败 ' + fail);
|
|||
|
|
if (fail > 0) (globalThis as { __fail?: boolean }).__fail = true;
|