247 lines
9.9 KiB
JavaScript
247 lines
9.9 KiB
JavaScript
const assert = require('node:assert/strict');
|
|
const fs = require('node:fs');
|
|
const path = require('node:path');
|
|
const test = require('node:test');
|
|
|
|
const modulePath = path.join(__dirname, '..', 'public', 'js', 'merit-pages.js');
|
|
const MeritPages = fs.existsSync(modulePath) ? require(modulePath) : {};
|
|
|
|
function requireFunction(name) {
|
|
assert.equal(typeof MeritPages[name], 'function', `缺少 MeritPages.${name}`);
|
|
return MeritPages[name];
|
|
}
|
|
|
|
test('功德录只从 URL 读取安全的真实家谱和功德记录编号', () => {
|
|
const getCurrentGenealogyId = requireFunction('getCurrentGenealogyId');
|
|
const getCurrentMeritId = requireFunction('getCurrentMeritId');
|
|
|
|
assert.equal(
|
|
getCurrentGenealogyId('?genealogyId=2060000000000000001&meritId=2060000000000000002'),
|
|
'2060000000000000001'
|
|
);
|
|
assert.equal(
|
|
getCurrentMeritId('?genealogyId=2060000000000000001&meritId=2060000000000000002'),
|
|
'2060000000000000002'
|
|
);
|
|
assert.equal(getCurrentGenealogyId(''), '');
|
|
assert.equal(getCurrentMeritId('?meritId=unsafe'), '');
|
|
});
|
|
|
|
test('功德记录写入只构造 YAML MeritRecordBody 并转换后端时间格式', () => {
|
|
const buildMeritRecordBody = requireFunction('buildMeritRecordBody');
|
|
|
|
assert.deepEqual(buildMeritRecordBody({
|
|
donorName: ' 叶明 ',
|
|
meritType: 'repair',
|
|
meritTitle: ' 修缮宗祠 ',
|
|
meritContent: ' 参与修缮 ',
|
|
amount: '500.50',
|
|
meritTime: '2026-07-26T10:00',
|
|
sortOrder: '3',
|
|
status: '0',
|
|
meritId: 'should-not-send',
|
|
appUserId: 'should-not-send',
|
|
mediaOssIds: 'should-not-send'
|
|
}), {
|
|
donorName: '叶明',
|
|
meritType: 'repair',
|
|
meritTitle: '修缮宗祠',
|
|
meritContent: '参与修缮',
|
|
amount: 500.5,
|
|
meritTime: '2026-07-26 10:00:00',
|
|
sortOrder: 3,
|
|
status: '0'
|
|
});
|
|
});
|
|
|
|
test('功德记录校验必填、枚举、金额精度、时间、排序和可重读状态', () => {
|
|
const buildMeritRecordBody = requireFunction('buildMeritRecordBody');
|
|
const validateMeritRecordBody = requireFunction('validateMeritRecordBody');
|
|
|
|
assert.equal(validateMeritRecordBody({ donorName: '', meritTitle: '标题' }), '请填写功德人姓名');
|
|
assert.equal(validateMeritRecordBody({ donorName: '叶明', meritTitle: '' }), '请填写功德标题');
|
|
assert.equal(
|
|
validateMeritRecordBody({ donorName: '叶明', meritTitle: '标题', meritType: 'service' }),
|
|
'功德类型无效'
|
|
);
|
|
assert.equal(
|
|
validateMeritRecordBody(buildMeritRecordBody({ donorName: '叶明', meritTitle: '标题', amount: '9007199254740993.01' })),
|
|
'功德金额超出浏览器可安全提交的精度'
|
|
);
|
|
assert.equal(
|
|
validateMeritRecordBody({ donorName: '叶明', meritTitle: '标题', meritTime: '2026-02-30 10:00:00' }),
|
|
'功德时间格式无效'
|
|
);
|
|
assert.equal(
|
|
validateMeritRecordBody(buildMeritRecordBody({ donorName: '叶明', meritTitle: '标题', sortOrder: '1.5' })),
|
|
'排序值必须是安全整数'
|
|
);
|
|
assert.equal(
|
|
validateMeritRecordBody({ donorName: '叶明', meritTitle: '标题', status: '1' }),
|
|
'当前 PC 无法重新读取停用功德记录,暂不开放停用'
|
|
);
|
|
assert.equal(
|
|
validateMeritRecordBody({ donorName: '叶明', meritTitle: '标题', meritType: 'public', amount: -500.25, status: '0' }),
|
|
''
|
|
);
|
|
});
|
|
|
|
test('功德记录响应使用 PC MeritRecordVo 并保留 BigDecimal 字符串', () => {
|
|
const normalizeMeritRecord = requireFunction('normalizeMeritRecord');
|
|
|
|
assert.deepEqual(normalizeMeritRecord({
|
|
meritId: '2060000000000000001',
|
|
genealogyId: '2060000000000000002',
|
|
genealogyNo: 'G20260729001',
|
|
genealogyName: '叶氏家谱',
|
|
surname: '叶',
|
|
appUserId: '2060000000000000003',
|
|
appUserNickName: '叶子',
|
|
appUserPhone: '19100000000',
|
|
donorName: '叶明',
|
|
meritType: 'repair',
|
|
meritTitle: '修缮宗祠',
|
|
meritContent: '参与修缮',
|
|
amount: '500.50',
|
|
meritTime: '2026-07-26 10:00:00',
|
|
sortOrder: 1,
|
|
status: '0',
|
|
remark: '年度功德'
|
|
}), {
|
|
meritId: '2060000000000000001',
|
|
genealogyId: '2060000000000000002',
|
|
genealogyNo: 'G20260729001',
|
|
genealogyName: '叶氏家谱',
|
|
surname: '叶',
|
|
appUserId: '2060000000000000003',
|
|
appUserNickName: '叶子',
|
|
appUserPhone: '19100000000',
|
|
donorName: '叶明',
|
|
meritType: 'repair',
|
|
meritTitle: '修缮宗祠',
|
|
meritContent: '参与修缮',
|
|
amount: '500.50',
|
|
meritTime: '2026-07-26 10:00:00',
|
|
sortOrder: 1,
|
|
status: '0',
|
|
remark: '年度功德'
|
|
});
|
|
assert.equal(normalizeMeritRecord({
|
|
meritId: Number('2060000000000000001'),
|
|
genealogyId: '2',
|
|
donorName: '叶明',
|
|
meritType: 'donation',
|
|
meritTitle: '捐赠',
|
|
amount: '1',
|
|
status: '0'
|
|
}), null);
|
|
});
|
|
|
|
test('功德记录详情转义内容并隐藏手机号和内部编号', () => {
|
|
const normalizeMeritRecord = requireFunction('normalizeMeritRecord');
|
|
const renderMeritDetail = requireFunction('renderMeritDetail');
|
|
const record = normalizeMeritRecord({
|
|
meritId: '1',
|
|
genealogyId: '2',
|
|
appUserId: '3',
|
|
appUserNickName: '叶子',
|
|
appUserPhone: '19100000000',
|
|
donorName: '<script>alert(1)</script>',
|
|
meritType: 'public',
|
|
meritTitle: '公益助学',
|
|
meritContent: '<img src=x onerror=alert(1)>资助学生',
|
|
amount: '500.50',
|
|
meritTime: '2026-07-26 10:00:00',
|
|
sortOrder: 1,
|
|
status: '0'
|
|
});
|
|
const html = renderMeritDetail(record);
|
|
|
|
assert.doesNotMatch(html, /<script>|<img/);
|
|
assert.match(html, /资助学生/);
|
|
assert.match(html, /公益/);
|
|
assert.match(html, /500\.50/);
|
|
assert.doesNotMatch(html, /19100000000|>1<|>2<|>3</);
|
|
});
|
|
|
|
test('功德记录写后重读必须返回同一条稳定记录', () => {
|
|
const matchesSavedMerit = requireFunction('matchesSavedMerit');
|
|
const detail = {
|
|
meritId: '2060000000000000001',
|
|
genealogyId: '2060000000000000002',
|
|
donorName: '叶明',
|
|
meritType: 'donation',
|
|
meritTitle: '捐赠',
|
|
amount: '500',
|
|
meritTime: '2026-07-26 10:00:00',
|
|
status: '0'
|
|
};
|
|
|
|
assert.equal(matchesSavedMerit(detail, '2060000000000000001'), true);
|
|
assert.equal(matchesSavedMerit(detail, '2060000000000000009'), false);
|
|
assert.equal(matchesSavedMerit({}, '2060000000000000001'), false);
|
|
});
|
|
|
|
test('功德记录编辑器拒绝不可重读的停用记录', () => {
|
|
const isEditableMerit = requireFunction('isEditableMerit');
|
|
|
|
assert.equal(isEditableMerit({ status: '0' }), true);
|
|
assert.equal(isEditableMerit({ status: '1' }), false);
|
|
assert.equal(isEditableMerit(null), false);
|
|
});
|
|
|
|
test('功德录页面按家谱能力开放 PC CRUD,且不制造附件和契约外类型', () => {
|
|
const projectRoot = path.join(__dirname, '..');
|
|
const listPage = fs.readFileSync(path.join(projectRoot, 'profile-merit.html'), 'utf8');
|
|
const editPage = fs.readFileSync(path.join(projectRoot, 'profile-merit-edit.html'), 'utf8');
|
|
const styles = fs.readFileSync(path.join(projectRoot, 'public', 'css', 'profile-module.css'), 'utf8');
|
|
|
|
assert.doesNotMatch(listPage, /data-feature-status="pending"|pending-pages\.js/);
|
|
assert.match(listPage, /data-merit-list/);
|
|
assert.match(listPage, /data-merit-detail/);
|
|
assert.match(listPage, /data-merit-create-link hidden/);
|
|
assert.doesNotMatch(editPage, /data-feature-status="pending"|pending-pages\.js/);
|
|
assert.match(editPage, /name="meritTime" type="datetime-local"/);
|
|
assert.match(editPage, /value="donation"/);
|
|
assert.match(editPage, /value="repair"/);
|
|
assert.match(editPage, /value="public"/);
|
|
assert.match(editPage, /value="other"/);
|
|
assert.doesNotMatch(editPage, /value="service"/);
|
|
assert.match(editPage, /name="amount" type="number" step="any"/);
|
|
assert.match(editPage, /name="sortOrder"/);
|
|
assert.match(editPage, /data-merit-form[^>]*novalidate/);
|
|
assert.match(editPage, /for="meritName">功德人姓名<span class="field-required"/);
|
|
assert.match(editPage, /for="meritTitle">功德标题<span class="field-required"/);
|
|
assert.match(editPage, /name="status" type="hidden" value="0"/);
|
|
assert.match(editPage, /data-merit-form-status role="status" aria-live="polite"/);
|
|
assert.match(editPage, /data-merit-editor-placeholder>[\s\S]*api-state--loading" role="status" aria-live="polite" aria-busy="true"/);
|
|
assert.doesNotMatch(editPage, /mediaOssIds|type="file"|upload-pages\.js/);
|
|
assert.match(editPage, /public\/js\/rich-editor\.js/);
|
|
assert.match(editPage, /public\/js\/merit-pages\.js/);
|
|
assert.match(styles, /\[data-merit-editor\]\[hidden\][^{]*\{[^}]*display:\s*none\s*!important/s);
|
|
});
|
|
|
|
test('功德记录只向内容维护者或管理员开放写操作,并在提交前同步富文本', () => {
|
|
const script = fs.readFileSync(modulePath, 'utf8');
|
|
|
|
assert.equal(requireFunction('canEditRecords')({ canEditContent: true }), true);
|
|
assert.equal(requireFunction('canEditRecords')({ canManage: true }), true);
|
|
assert.equal(requireFunction('canEditRecords')({ canEditContent: false, canManage: false }), false);
|
|
assert.match(script, /await loadCapability\(api, genealogyId\);/);
|
|
assert.match(script, /if \(writePending \|\| !canEditContent \|\| !genealogyId/);
|
|
assert.match(script, /root\.AppRichEditor && root\.AppRichEditor\.syncAll/);
|
|
assert.match(script, /if \(canEditContent\) \{[\s\S]*data-merit-delete-id/);
|
|
});
|
|
|
|
test('功德记录使用统一的加载、空数据、失败和无权限状态,以及提交中的按钮反馈', () => {
|
|
const script = fs.readFileSync(modulePath, 'utf8');
|
|
|
|
assert.match(script, /function getApiStateType\(error\)[\s\S]*isForbidden\(error\) \? 'forbidden' : 'error'/);
|
|
assert.match(script, /setState\(query\('\[data-merit-list\]'\), 'loading', '正在加载功德记录…'\)/);
|
|
assert.match(script, /setState\(query\('\[data-merit-detail\]'\), 'loading', '正在加载功德记录详情…'\)/);
|
|
assert.match(script, /setFormStatus\('正在读取家谱权限…', 'loading'\)/);
|
|
assert.match(script, /setFormStatus\(validation, 'error'\)/);
|
|
assert.match(script, /setWritePending\(true, '正在保存…'\)/);
|
|
assert.match(script, /setFormStatus\('正在保存功德记录…', 'loading'\)/);
|
|
});
|