Files
blog/blog-admin/tools/mailtest.ts
T
zqlit a74b3c7127
Deploy to Production / pre-check (push) Successful in 58s
Deploy to Production / build (push) Successful in 4m3s
Deploy to Production / deploy-edgeone (push) Successful in 3m48s
Deploy to Production / finalize (push) Successful in 26s
Deploy to Production / notify-failure (push) Skipped
归档 artalk-cf 评论后端 + rss-robot 到 blog-admin(含技术选型/模块分布 README)
2026-10-04 08:45:40 +08:00

81 lines
3.4 KiB
TypeScript
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.
/* 邮件模板自检:验证渲染不炸、转义生效、关键字段都在。
* 跑法(见 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;