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', 'ceremony-admin-pages.js');
const CeremonyAdminPages = fs.existsSync(modulePath) ? require(modulePath) : {};
function requireFunction(name) {
assert.equal(typeof CeremonyAdminPages[name], 'function', `缺少 CeremonyAdminPages.${name}`);
return CeremonyAdminPages[name];
}
test('祭祀管理页只从上下文读取安全的家谱和活动 ID', () => {
const getCurrentGenealogyId = requireFunction('getCurrentGenealogyId');
const getCurrentCeremonyId = requireFunction('getCurrentCeremonyId');
assert.equal(getCurrentGenealogyId('?genealogyId=2060000000000000001'), '2060000000000000001');
assert.equal(getCurrentCeremonyId('?ceremonyId=2060000000000000002'), '2060000000000000002');
assert.equal(getCurrentCeremonyId('?ceremonyId=unsafe'), '');
});
test('活动写入只构造 CeremonyBody 并固定正常状态', () => {
const buildCeremonyBody = requireFunction('buildCeremonyBody');
assert.deepEqual(buildCeremonyBody({
ceremonyType: ' ancestor ',
ceremonyTitle: ' 清明祭祖 ',
ceremonyDesc: ' 缅怀先祖 ',
ceremonyTime: '2026-04-04T09:00',
location: ' 祠堂 ',
locationAddress: ' 四川省成都市示例路1号 ',
longitude: '104.066541',
latitude: '30.572269',
coverOssId: '2060000000000000003',
sortOrder: '1',
status: '1',
ceremonyId: 'must-drop',
giftAmount: 999
}), {
ceremonyType: 'ancestor',
ceremonyTitle: '清明祭祖',
ceremonyDesc: '缅怀先祖',
ceremonyTime: '2026-04-04 09:00:00',
location: '祠堂',
locationAddress: '四川省成都市示例路1号',
longitude: 104.066541,
latitude: 30.572269,
coverOssId: '2060000000000000003',
sortOrder: 1,
status: '0'
});
});
test('活动校验必填字段、成对坐标、范围、长度和上传派生 ID', () => {
const buildCeremonyBody = requireFunction('buildCeremonyBody');
const validateCeremonyBody = requireFunction('validateCeremonyBody');
assert.equal(validateCeremonyBody({ ceremonyType: '', ceremonyTitle: '', status: '0' }), '请填写活动类型');
assert.equal(validateCeremonyBody({ ceremonyType: 'ancestor', ceremonyTitle: '', status: '0' }), '请填写活动标题');
assert.equal(
validateCeremonyBody(buildCeremonyBody({ ceremonyType: 'ancestor', ceremonyTitle: '祭祖', longitude: '104' })),
'经度和纬度必须同时提供'
);
assert.equal(
validateCeremonyBody(buildCeremonyBody({ ceremonyType: 'ancestor', ceremonyTitle: '祭祖', longitude: '181', latitude: '30' })),
'经度范围必须是 -180 到 180'
);
assert.equal(
validateCeremonyBody(buildCeremonyBody({ ceremonyType: 'ancestor', ceremonyTitle: '祭祖', locationAddress: '地'.repeat(301) })),
'详细地址不能超过 300 个字符'
);
assert.equal(
validateCeremonyBody(buildCeremonyBody({ ceremonyType: 'ancestor', ceremonyTitle: '祭祖', coverOssId: Number.MAX_SAFE_INTEGER + 1 })),
'封面文件编号无效,请重新选择文件'
);
});
test('祭品写入只提交 CeremonyGiftBody 并校验后端非负金额', () => {
const buildCeremonyGiftBody = requireFunction('buildCeremonyGiftBody');
const validateCeremonyGiftBody = requireFunction('validateCeremonyGiftBody');
assert.deepEqual(buildCeremonyGiftBody({
giverName: ' 叶子 ',
giftAmount: '66.66',
giftMessage: ' 缅怀先祖 ',
giverUserId: 'must-drop',
giftTime: 'must-drop'
}), {
giverName: '叶子',
giftAmount: 66.66,
giftMessage: '缅怀先祖'
});
assert.equal(validateCeremonyGiftBody(buildCeremonyGiftBody({ giftAmount: '' })), '请填写礼金金额');
assert.equal(validateCeremonyGiftBody(buildCeremonyGiftBody({ giftAmount: '-0.01' })), '礼金金额不能小于 0');
assert.equal(validateCeremonyGiftBody(buildCeremonyGiftBody({ giftAmount: 'not-number' })), '礼金金额必须是有效数字');
});
test('活动、祭品和邀请响应使用完整 PC VO 并拒绝不安全 ID', () => {
const normalizeCeremony = requireFunction('normalizeCeremony');
const normalizeCeremonyGift = requireFunction('normalizeCeremonyGift');
const normalizeInvitation = requireFunction('normalizeInvitation');
const ceremony = normalizeCeremony({
ceremonyId: '2060000000000000001',
genealogyId: '2060000000000000002',
sponsorUserId: '2060000000000000003',
sponsorNickName: '叶子',
sponsorPhone: '测试联系方式',
ceremonyType: 'ancestor',
ceremonyTitle: '清明祭祖',
ceremonyDesc: '缅怀先祖',
ceremonyTime: '2026-04-04 09:00:00',
location: '祠堂',
locationAddress: '成都',
longitude: 104.066541,
latitude: 30.572269,
coverOssId: '2060000000000000004',
giftCount: 2,
giftAmount: 88.88,
sortOrder: 1,
status: '0',
remark: ''
});
const gift = normalizeCeremonyGift({
giftId: '2060000000000000005',
genealogyId: '2060000000000000002',
ceremonyId: '2060000000000000001',
ceremonyTitle: '清明祭祖',
giverUserId: '2060000000000000006',
giverNickName: '宗亲',
giverPhone: '19100000000',
giverName: '叶先生',
giftAmount: 66.66,
giftMessage: '缅怀先祖',
giftTime: '2026-04-04 10:00:00',
status: '0',
remark: ''
});
const invitation = normalizeInvitation({
invitationId: '2060000000000000007',
genealogyId: '2060000000000000002',
ceremonyId: '2060000000000000001',
inviteeUserId: '2060000000000000006',
inviteStatus: 'PENDING',
inviteVersion: 1
});
assert.equal(ceremony.ceremonyId, '2060000000000000001');
assert.equal(ceremony.giftCount, 2);
assert.equal(gift.giftId, '2060000000000000005');
assert.equal(invitation.inviteeUserId, '2060000000000000006');
assert.equal(normalizeCeremony({
ceremonyId: Number('2060000000000000001'),
genealogyId: '2',
ceremonyType: 'ancestor',
ceremonyTitle: '祭祖',
status: '0'
}), null);
});
test('管理员邀约名单只使用正常成员中的真实 appUserId 并去重', () => {
const normalizeInviteeOption = requireFunction('normalizeInviteeOption');
const buildInviteesBody = requireFunction('buildInviteesBody');
const normal = normalizeInviteeOption({
memberId: '1',
genealogyId: '2',
appUserId: '2060000000000000001',
appUserNickName: '宗亲',
memberName: '叶先生',
status: '0'
});
assert.equal(normal.appUserId, '2060000000000000001');
assert.equal(normalizeInviteeOption({
memberId: '1',
genealogyId: '2',
appUserId: null,
memberName: '未绑定成员',
status: '0'
}), null);
assert.equal(normalizeInviteeOption({
memberId: '1',
genealogyId: '2',
appUserId: '3',
memberName: '停用成员',
status: '1'
}), null);
assert.deepEqual(buildInviteesBody([
'2060000000000000001',
'2060000000000000001',
'2060000000000000002',
'unsafe'
]), {
inviteeUserIds: ['2060000000000000001', '2060000000000000002']
});
});
test('活动和祭品展示转义内容且不暴露手机号、OSS ID 或内部 ID', () => {
const normalizeCeremony = requireFunction('normalizeCeremony');
const normalizeCeremonyGift = requireFunction('normalizeCeremonyGift');
const renderCeremonyDetail = requireFunction('renderCeremonyDetail');
const renderGiftList = requireFunction('renderGiftList');
const html = renderCeremonyDetail(normalizeCeremony({
ceremonyId: '2060000000000000001',
genealogyId: '2060000000000000002',
sponsorUserId: '2060000000000000003',
sponsorNickName: '
叶子',
sponsorPhone: '测试联系方式',
ceremonyType: 'ancestor',
ceremonyTitle: '祭祖',
coverOssId: '2060000000000000004',
giftCount: 1,
giftAmount: 66.66,
status: '0'
})) + renderGiftList([normalizeCeremonyGift({
giftId: '2060000000000000005',
genealogyId: '2060000000000000002',
ceremonyId: '2060000000000000001',
giverUserId: '2060000000000000006',
giverNickName: '
宗亲',
giverPhone: '19100000000',
giverName: '叶先生',
giftAmount: 66.66,
giftMessage: '缅怀',
status: '0'
})]);
assert.doesNotMatch(html, /