Files
jiapu/tests/memo-pages.test.js
T
fizzleaf fb1743aa2a feat(api): 完善家谱系统API客户端契约
- 实现家谱管理相关方法,包括创建、详情、概览、我的家谱和选项查询
- 添加家谱加入申请功能,支持申请、审核、取消和待审核列表操作
- 集成通知详情获取方法和通知ID安全验证机制
- 完善功德记录、谱文、相册、视频、祭祀活动的完整CRUD操作契约
- 实现家谱成员管理功能,包含成员列表、更新、移除和转让所有者操作
- 优化路径ID验证逻辑,拒绝不安全的数值ID并提供明确错误提示
- 更新测试用例以验证所有新增API方法的路径和请求体白名单机制
2026-07-29 16:57:27 +08:00

185 lines
6.6 KiB
JavaScript

const assert = require('node:assert/strict');
const fs = require('node:fs');
const path = require('node:path');
const test = require('node:test');
const MemoPages = require('../public/js/memo-pages.js');
test('备忘录页只从 URL 读取安全的真实家谱和备忘录编号', () => {
assert.equal(
MemoPages.getCurrentGenealogyId('?genealogyId=2060000000000000001&memoId=2060000000000000002'),
'2060000000000000001'
);
assert.equal(
MemoPages.getCurrentMemoId('?genealogyId=2060000000000000001&memoId=2060000000000000002'),
'2060000000000000002'
);
assert.equal(MemoPages.getCurrentGenealogyId(''), '');
assert.equal(MemoPages.getCurrentMemoId('?memoId=unsafe'), '');
});
test('备忘录写入只构造 YAML MemoBody 并转换后端时间格式', () => {
assert.deepEqual(MemoPages.buildMemoBody({
memoTitle: ' 祭祖提醒 ',
memoContent: ' 准备供品 ',
remindTime: '2026-07-24T09:30',
completed: '1',
mediaOssIds: '2060000000000000003,2060000000000000004',
sortOrder: '3',
status: '0',
memoId: 'should-not-send',
appUserId: 'should-not-send'
}), {
memoTitle: '祭祖提醒',
memoContent: '准备供品',
remindTime: '2026-07-24 09:30:00',
completed: '1',
mediaOssIds: '2060000000000000003,2060000000000000004',
sortOrder: 3,
status: '0'
});
});
test('备忘录校验标题、时间、完成状态、附件、排序和可重读状态', () => {
assert.equal(MemoPages.validateMemoBody({ memoTitle: '' }), '请填写备忘录标题');
assert.equal(
MemoPages.validateMemoBody({ memoTitle: '提醒', remindTime: '2026-07-24T09:30:00+08:00' }),
'提醒时间格式无效'
);
assert.equal(
MemoPages.validateMemoBody({ memoTitle: '提醒', remindTime: '2026-02-30 09:30:00' }),
'提醒时间格式无效'
);
assert.equal(
MemoPages.validateMemoBody({ memoTitle: '提醒', completed: '2' }),
'完成状态只能是未完成或已完成'
);
assert.equal(
MemoPages.validateMemoBody({ memoTitle: '提醒', mediaOssIds: '101, 102' }),
'附件上传结果无效'
);
assert.equal(
MemoPages.validateMemoBody(MemoPages.buildMemoBody({ memoTitle: '提醒', sortOrder: '1.5' })),
'排序值必须是安全整数'
);
assert.equal(
MemoPages.validateMemoBody({ memoTitle: '提醒', status: '1' }),
'当前 PC 无法重新读取停用备忘录,暂不开放停用'
);
assert.equal(MemoPages.validateMemoBody({ memoTitle: '提醒', completed: '0', status: '0' }), '');
assert.equal(MemoPages.validateMemoBody({ memoTitle: '提醒', completed: '1', status: '0' }), '');
});
test('备忘录响应使用 PC MemoVo 并拒绝不安全的数字长整型', () => {
assert.deepEqual(MemoPages.normalizeMemo({
memoId: '2060000000000000001',
genealogyId: '2060000000000000002',
genealogyNo: 'G20260729001',
genealogyName: '叶氏家谱',
surname: '叶',
appUserId: '2060000000000000003',
appUserNickName: '叶子',
appUserPhone: '19100000000',
memoTitle: '祭祖提醒',
memoContent: '准备供品',
remindTime: '2026-07-24 09:30:00',
completed: '1',
mediaOssIds: '2060000000000000004',
sortOrder: 1,
status: '0',
remark: '年度事项'
}), {
memoId: '2060000000000000001',
genealogyId: '2060000000000000002',
genealogyNo: 'G20260729001',
genealogyName: '叶氏家谱',
surname: '叶',
appUserId: '2060000000000000003',
appUserNickName: '叶子',
appUserPhone: '19100000000',
memoTitle: '祭祖提醒',
memoContent: '准备供品',
remindTime: '2026-07-24 09:30:00',
completed: '1',
mediaOssIds: '2060000000000000004',
sortOrder: 1,
status: '0',
remark: '年度事项'
});
assert.equal(MemoPages.normalizeMemo({
memoId: Number('2060000000000000001'),
genealogyId: '2',
memoTitle: '提醒',
completed: '0',
status: '0'
}), null);
});
test('备忘录渲染业务字段但不暴露手机号、OSS ID 或未转义内容', () => {
const memo = MemoPages.normalizeMemo({
memoId: '1',
genealogyId: '2',
appUserId: '3',
appUserNickName: '叶子',
appUserPhone: '19100000000',
memoTitle: '<script>alert(1)</script>',
memoContent: '<img src=x onerror=alert(1)>准备供品',
remindTime: '2026-07-24 09:30:00',
completed: '1',
mediaOssIds: '4,5',
sortOrder: 1,
status: '0',
remark: '年度事项'
});
const html = MemoPages.renderMemoDetail(memo);
assert.doesNotMatch(html, /<script>|<img/);
assert.match(html, /准备供品/);
assert.match(html, /已完成/);
assert.match(html, /2 个附件/);
assert.doesNotMatch(html, /19100000000|>4<|>5</);
});
test('备忘录写后重读必须返回同一条稳定记录', () => {
const detail = {
memoId: '2060000000000000001',
genealogyId: '2060000000000000002',
memoTitle: '祭祖提醒',
completed: '0',
status: '0'
};
assert.equal(MemoPages.matchesSavedMemo(detail, '2060000000000000001'), true);
assert.equal(MemoPages.matchesSavedMemo(detail, '2060000000000000009'), false);
assert.equal(MemoPages.matchesSavedMemo({}, '2060000000000000001'), false);
});
test('备忘录编辑器拒绝不可重读的停用记录', () => {
assert.equal(MemoPages.isEditableMemo({ status: '0' }), true);
assert.equal(MemoPages.isEditableMemo({ status: '1' }), false);
assert.equal(MemoPages.isEditableMemo(null), false);
});
test('备忘录列表和编辑页开放 PC CRUD 且不允许手填 OSS ID 或业务状态', () => {
const root = path.join(__dirname, '..');
const listPage = fs.readFileSync(path.join(root, 'profile-memo.html'), 'utf8');
const editPage = fs.readFileSync(path.join(root, 'profile-memo-edit.html'), 'utf8');
const styles = fs.readFileSync(path.join(root, 'public', 'css', 'profile-module.css'), 'utf8');
assert.match(listPage, /data-memo-list/);
assert.match(listPage, /data-memo-detail/);
assert.doesNotMatch(listPage, /原始 JSON|不开放编辑和删除/);
assert.match(editPage, /name="remindTime" type="datetime-local"/);
assert.match(editPage, /name="completed"/);
assert.match(editPage, /value="0"/);
assert.match(editPage, /value="1"/);
assert.match(editPage, /name="status" type="hidden" value="0"/);
assert.match(editPage, /name="mediaOssIds" type="hidden"/);
assert.match(editPage, /data-memo-clear-media/);
assert.doesNotMatch(editPage, /name="(?:mediaOssIds|status)"[^>]*type="text"/);
assert.match(editPage, /public\/js\/md5\.js/);
assert.match(editPage, /public\/js\/upload-pages\.js/);
assert.match(editPage, /public\/js\/memo-pages\.js/);
assert.match(styles, /\[data-memo-editor\]\[hidden\][^{]*\{[^}]*display:\s*none\s*!important/s);
});