归档 artalk-cf 评论后端 + rss-robot 到 blog-admin(含技术选型/模块分布 README)
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
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
This commit is contained in:
1 parent
855a001046
commit
a74b3c7127
98 files changed
+15657
No files matched your search
@@ -0,0 +1,117 @@
|
||||
/**
|
||||
* 自检:不依赖 wrangler / 网络,直接验证最容易出错的那几块纯逻辑。
|
||||
* node node_modules/.bin/esbuild tools/selftest.ts --bundle --platform=node --format=esm --outfile=tools/.selftest.mjs && node tools/.selftest.mjs
|
||||
*/
|
||||
import { md5, md5Lower } from '../src/lib/md5';
|
||||
import { formatDateCN } from '../src/lib/util';
|
||||
import { renderMarkdown } from '../src/lib/md';
|
||||
import { hashPassword, verifyPassword, signToken, verifyToken } from '../src/lib/session';
|
||||
import { parseSimpleYaml } from '../src/routes/admin';
|
||||
import type { Env } from '../src/types';
|
||||
|
||||
let pass = 0;
|
||||
let fail = 0;
|
||||
|
||||
function eq(name: string, actual: unknown, expected: unknown) {
|
||||
const a = JSON.stringify(actual);
|
||||
const e = JSON.stringify(expected);
|
||||
if (a === e) {
|
||||
pass++;
|
||||
console.log(` PASS ${name}`);
|
||||
} else {
|
||||
fail++;
|
||||
console.log(` FAIL ${name}\n 实际: ${a}\n 期望: ${e}`);
|
||||
}
|
||||
}
|
||||
|
||||
function ok(name: string, cond: boolean) {
|
||||
eq(name, cond, true);
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------ MD5
|
||||
console.log('== MD5(Gravatar hash,必须与官方一致) ==');
|
||||
eq('md5("")', md5(''), 'd41d8cd98f00b204e9800998ecf8427e');
|
||||
eq('md5("abc")', md5('abc'), '900150983cd24fb0d6963f7d28e17f72');
|
||||
eq('md5("test@example.com")', md5('test@example.com'), '55502f40dc8b7c769880b10874abc9d0');
|
||||
eq('md5("z@usj.cc")', md5('z@usj.cc'), '1cce0a22c2c7648eab76ec876c6a54d9');
|
||||
eq('md5("优世界") 多字节', md5('优世界'), '7fc1dce97eead18fa9e9ae4fbb8044cb');
|
||||
eq('md5 长输入(跨 block)', md5('a'.repeat(200)), '887f30b43b2867f4a9accceee7d16e6c');
|
||||
eq('md5Lower 去空格+小写', md5Lower(' Z@USJ.CC '), '1cce0a22c2c7648eab76ec876c6a54d9');
|
||||
|
||||
async function main() {
|
||||
// ------------------------------------------------------------------ 时间
|
||||
console.log('== 时间格式(Asia/Shanghai) ==');
|
||||
eq('1631363636000', formatDateCN(1631363636000), '2021-09-11 20:33:56');
|
||||
eq('0', formatDateCN(0), '1970-01-01 08:00:00');
|
||||
|
||||
// ------------------------------------------------------------------ Markdown
|
||||
console.log('== Markdown 渲染 ==');
|
||||
ok('<script> 被转义', !renderMarkdown('<script>alert(1)</script>').includes('<script>'));
|
||||
ok('img onerror 被转义', !renderMarkdown('<img src=x onerror=alert(1)>').includes('onerror='));
|
||||
ok('javascript: 链接被剔除', !renderMarkdown('[x](javascript:alert(1))').includes('javascript:'));
|
||||
ok('**粗体**', renderMarkdown('**hi**').includes('<strong>hi</strong>'));
|
||||
ok('`行内代码`', renderMarkdown('`code`').includes('<code>code</code>'));
|
||||
ok('裸链接可点', renderMarkdown('见 https://usj.cc 一游').includes('<a href="https://usj.cc"'));
|
||||
ok('单换行 → <br>', renderMarkdown('a\nb').includes('a<br>b'));
|
||||
ok('``` 代码块', renderMarkdown('```js\nlet a=1;\n```').includes('<pre><code class="language-js">'));
|
||||
ok('& 被转义', renderMarkdown('a & b').includes('a & b'));
|
||||
ok('含单引号的正文不炸', renderMarkdown("it's fine").includes('it's fine'));
|
||||
|
||||
// ------------------------------------------------------------------ 密码
|
||||
console.log('== 密码(PBKDF2 / md5 / 明文兼容) ==');
|
||||
const hashed = await hashPassword('s3cret-密码');
|
||||
ok('hash 前缀是 (pbkdf2)', hashed.startsWith('(pbkdf2)'));
|
||||
ok('正确密码通过', await verifyPassword(hashed, 's3cret-密码'));
|
||||
ok('错误密码拒绝', !(await verifyPassword(hashed, 'wrong')));
|
||||
ok('空密码拒绝', !(await verifyPassword('', '')));
|
||||
ok('兼容 (md5) 旧密码', await verifyPassword('(md5)1cce0a22c2c7648eab76ec876c6a54d9', 'z@usj.cc'));
|
||||
ok('兼容明文旧密码', await verifyPassword('plain', 'plain'));
|
||||
ok('(bcrypt) 明确不支持', !(await verifyPassword('(bcrypt)$2a$10$xxx', 'whatever')));
|
||||
|
||||
// ------------------------------------------------------------------ Token
|
||||
console.log('== 登录 token(HMAC 无状态) ==');
|
||||
const env = { TOKEN_SECRET: 'test-secret-please-change' } as Env;
|
||||
const token = await signToken(env, 42);
|
||||
ok('token 是两段式', token.split('.').length === 2);
|
||||
const payload = await verifyToken(env, token);
|
||||
eq('解出的 uid', payload?.uid, 42);
|
||||
ok(
|
||||
'改一位就失效',
|
||||
!(await verifyToken(env, token.slice(0, -2) + (token.endsWith('AA') ? 'BB' : 'AA'))),
|
||||
);
|
||||
ok('换密钥就失效', !(await verifyToken({ TOKEN_SECRET: 'other' } as Env, token)));
|
||||
ok('乱串被拒', !(await verifyToken(env, 'not-a-token')));
|
||||
ok('空串被拒', !(await verifyToken(env, '')));
|
||||
|
||||
// ------------------------------------------------------------------ YAML
|
||||
console.log('== 设置模板 YAML 解析 ==');
|
||||
const tpl = `host: "0.0.0.0"
|
||||
port: 23366
|
||||
captcha:
|
||||
enabled: false
|
||||
mode: off # 关闭验证码
|
||||
moderator:
|
||||
pending_default: true
|
||||
ip_region:
|
||||
enabled: false
|
||||
frontend:
|
||||
pvAdd: true
|
||||
locale: zh-CN
|
||||
pluginURLs: []
|
||||
site_default: 优世界
|
||||
`;
|
||||
const parsed = parseSimpleYaml(tpl) as any;
|
||||
eq('顶层字符串', parsed.host, '0.0.0.0');
|
||||
eq('顶层数字', parsed.port, 23366);
|
||||
eq('嵌套布尔', parsed.captcha.enabled, false);
|
||||
eq('行内注释被剔除', parsed.captcha.mode, 'off');
|
||||
eq('嵌套布尔2', parsed.moderator.pending_default, true);
|
||||
eq('三层取值', parsed.frontend.pvAdd, true);
|
||||
eq('中文值', parsed.site_default, '优世界');
|
||||
eq('空数组', parsed.frontend.pluginURLs, []);
|
||||
|
||||
console.log(`\n通过 ${pass} / 失败 ${fail}`);
|
||||
process.exit(fail === 0 ? 0 : 1);
|
||||
}
|
||||
|
||||
void main();
|
||||
Reference in new issue
Block a user