const assert = require('node:assert/strict'); const fs = require('node:fs'); const path = require('node:path'); const test = require('node:test'); const HelpPages = require('../public/js/help-pages.js'); const helpId = '2062179707935264769'; function read(relativePath) { return fs.readFileSync(path.resolve(__dirname, '..', relativePath), 'utf8'); } function helpFixture(overrides) { return Object.assign({ helpId, helpCategory: '账号与安全', helpTitle: '如何修改登录密码?', helpContent: '进入账号安全页,完成验证后修改密码。', coverOssId: '2062179707935264701', sortOrder: 10, viewCount: 12, status: '0', remark: 'internal-only' }, overrides); } test('帮助文章只保留 HelpArticleVo 的安全展示字段并保持长 ID 字符串', () => { assert.deepEqual(HelpPages.normalizeHelpArticle(helpFixture()), { helpId, helpCategory: '账号与安全', helpTitle: '如何修改登录密码?', helpContent: '进入账号安全页,完成验证后修改密码。', viewCount: '12' }); assert.equal(HelpPages.normalizeHelpArticle(helpFixture({ helpId: Number.MAX_SAFE_INTEGER + 1 })), null); assert.equal(HelpPages.normalizeHelpArticle(helpFixture({ viewCount: '9007199254740993123' })).viewCount, '9007199254740993123'); }); test('帮助文章拒绝停用、空标题、空正文和非法浏览量', () => { assert.equal(HelpPages.normalizeHelpArticle(helpFixture({ status: '1' })), null); assert.equal(HelpPages.normalizeHelpArticle(helpFixture({ helpTitle: ' ' })), null); assert.equal(HelpPages.normalizeHelpArticle(helpFixture({ helpContent: '' })), null); assert.equal(HelpPages.normalizeHelpArticle(helpFixture({ viewCount: -1 })), null); assert.equal(HelpPages.normalizeHelpArticle(helpFixture({ viewCount: '1.5' })), null); }); test('帮助文章列表只接受直接数组且任一非法元素使整批失败', () => { assert.equal(HelpPages.normalizeHelpArticleList([helpFixture()]).length, 1); assert.deepEqual(HelpPages.normalizeHelpArticleList([]), []); assert.deepEqual(HelpPages.normalizeHelpArticleList({ rows: [helpFixture()] }), []); assert.deepEqual(HelpPages.normalizeHelpArticleList([ helpFixture(), helpFixture({ helpId: '' }) ]), []); }); test('帮助文章列表和详情转义响应且不暴露 OSS ID、排序、状态和备注', () => { const article = helpFixture({ helpTitle: '', helpContent: '\n第二行', helpCategory: '分类', coverOssId: 'secret-oss', remark: 'secret-remark' }); const listHtml = HelpPages.renderHelpArticleList([article]); const detailHtml = HelpPages.renderHelpArticleDetail(article); assert.match(listHtml, /<script>/); assert.match(detailHtml, /<img src=x onerror=alert\(1\)>
第二行/); assert.doesNotMatch(listHtml + detailHtml, / { const calls = []; const api = { async helpArticleDetail(id) { calls.push(id); return helpFixture(); } }; assert.equal((await HelpPages.loadHelpArticleDetail(api, helpId)).helpId, helpId); assert.deepEqual(calls, [helpId]); await assert.rejects( HelpPages.loadHelpArticleDetail({ async helpArticleDetail() { return helpFixture({ helpId: '2062179707935264770' }); } }, helpId), /详情响应与请求编号不一致/ ); }); test('帮助中心只把缺失登录态和 401 视为登录失效', () => { assert.equal(HelpPages.shouldRedirectToLogin(null), true); assert.equal(HelpPages.shouldRedirectToLogin({ getToken() { return ''; } }), true); assert.equal(HelpPages.shouldRedirectToLogin({ getToken() { return 'token'; } }, { status: 401 }), true); assert.equal(HelpPages.shouldRedirectToLogin({ getToken() { return 'token'; } }, { status: 403 }), false); }); test('帮助中心使用真实 PC 列表和详情,不再展示硬编码问答', () => { const page = read('help.html'); assert.match(page, /data-help-list/); assert.match(page, /data-help-status/); assert.match(page, /public\/js\/help-pages\.js/); assert.doesNotMatch(page, /如何创建第一本家谱|如何邀请亲人一起维护/); assert.doesNotMatch(page, /data-feature-status="pending"|pending-pages\.js/); });