feat(api): 添加帮助中心反馈系统和VIP服务功能
- 在ApiClient中新增submitFeedback、myFeedback、helpArticles、helpArticleDetail、 siteArticles、promotions、vipPackages、createVipOrder、vipOrders等方法 - 添加帮助文章和站点资讯的参数验证逻辑 - 更新测试文件添加新的API方法测试用例 - 在HTML页面中添加反馈、帮助和VIP服务相关页面的脚本引用 - 更新加入家谱页面为完整的申请流程界面 - 修改资讯详情页面为站点资讯展示页面 - 更新AxiosRequestUtil中认证处理逻辑 - 添加世系树渲染的HTML生成函数用于页面复用 - 更新文档中的API契约说明和页面规划
This commit is contained in:
@@ -0,0 +1,116 @@
|
||||
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: '<script>alert(1)</script>',
|
||||
helpContent: '<img src=x onerror=alert(1)>\n第二行',
|
||||
helpCategory: '<b>分类</b>',
|
||||
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\)><br \/>第二行/);
|
||||
assert.doesNotMatch(listHtml + detailHtml, /<script|<img|secret-oss|secret-remark|coverOssId|sortOrder|status|remark/);
|
||||
});
|
||||
|
||||
test('帮助文章详情必须与列表中点击的真实 helpId 一致', async () => {
|
||||
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/);
|
||||
});
|
||||
Reference in New Issue
Block a user