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', 'member-admin-pages.js');
const MemberAdminPages = fs.existsSync(modulePath) ? require(modulePath) : {};
function requireFunction(name) {
assert.equal(typeof MemberAdminPages[name], 'function', `缺少 MemberAdminPages.${name}`);
return MemberAdminPages[name];
}
test('成员管理页只从家谱上下文读取安全 ID', () => {
const getCurrentGenealogyId = requireFunction('getCurrentGenealogyId');
assert.equal(getCurrentGenealogyId('?genealogyId=2060000000000000001'), '2060000000000000001');
assert.equal(getCurrentGenealogyId('?genealogyId=unsafe'), '');
assert.equal(getCurrentGenealogyId(''), '');
});
test('成员响应使用完整 GenealogyMemberVo 并保持长 ID 为字符串', () => {
const normalizeMember = requireFunction('normalizeMember');
const member = normalizeMember({
memberId: '2060000000000000001',
genealogyId: '2060000000000000002',
appUserId: '2060000000000000003',
lineagePersonId: '2060000000000000004',
appUserNickName: '明远',
appUserPhone: '19100000000',
lineagePersonName: '李明',
memberName: '李先生',
roleType: 'member',
relationName: '族亲',
joinSource: 'invite',
inviterUserId: '2060000000000000005',
inviterNickName: '谱主',
inviterPhone: '19111111111',
joinTime: '2026-07-29 10:00:00',
status: '0'
});
assert.equal(member.memberId, '2060000000000000001');
assert.equal(member.appUserId, '2060000000000000003');
assert.equal(member.lineagePersonId, '2060000000000000004');
assert.equal(normalizeMember({
memberId: Number('2060000000000000001'),
genealogyId: '2',
appUserId: '3',
roleType: 'member',
status: '0'
}), null);
});
test('成员修改只构造后端可更新字段并收紧角色枚举', () => {
const buildMemberUpdateBody = requireFunction('buildMemberUpdateBody');
const validateMemberUpdateBody = requireFunction('validateMemberUpdateBody');
assert.deepEqual(buildMemberUpdateBody({
memberName: ' 李先生 ',
relationName: ' 族亲 ',
roleType: 'editor',
lineagePersonId: '2060000000000000004',
memberId: 'must-drop',
appUserId: 'must-drop',
status: '3'
}), {
memberName: '李先生',
relationName: '族亲',
roleType: 'editor',
lineagePersonId: '2060000000000000004'
});
assert.equal(validateMemberUpdateBody(buildMemberUpdateBody({ memberName: '名'.repeat(51) })), '成员名称不能超过 50 个字符');
assert.equal(validateMemberUpdateBody(buildMemberUpdateBody({ relationName: '关系'.repeat(51) })), '关系名称不能超过 100 个字符');
assert.throws(
() => buildMemberUpdateBody({ roleType: 'owner' }),
/成员角色只能是管理员、编辑或成员/
);
assert.equal(
validateMemberUpdateBody(buildMemberUpdateBody({ lineagePersonId: Number.MAX_SAFE_INTEGER + 1 })),
'世系人物选项无效'
);
});
test('成员管理动作遵守谱主和管理员的后端权限边界', () => {
const canEditMember = requireFunction('canEditMember');
const canRemoveMember = requireFunction('canRemoveMember');
const canLeaveGenealogy = requireFunction('canLeaveGenealogy');
const canTransferOwner = requireFunction('canTransferOwner');
assert.equal(canEditMember('owner', 'admin'), true);
assert.equal(canEditMember('admin', 'admin'), false);
assert.equal(canEditMember('admin', 'member'), true);
assert.equal(canEditMember('owner', 'owner'), false);
assert.equal(canRemoveMember('owner', 'owner'), false);
assert.equal(canRemoveMember('admin', 'admin'), false);
assert.equal(canLeaveGenealogy('owner'), false);
assert.equal(canLeaveGenealogy('member'), true);
assert.equal(canTransferOwner('owner'), true);
assert.equal(canTransferOwner('admin'), false);
});
test('谱主转移只使用成员列表中的真实目标 memberId', () => {
const buildOwnerTransferBody = requireFunction('buildOwnerTransferBody');
const validateOwnerTransferBody = requireFunction('validateOwnerTransferBody');
assert.deepEqual(buildOwnerTransferBody('2060000000000000001'), {
targetMemberId: '2060000000000000001'
});
assert.equal(validateOwnerTransferBody(buildOwnerTransferBody('')), '请选择新谱主');
assert.equal(validateOwnerTransferBody(buildOwnerTransferBody('unsafe')), '请选择新谱主');
assert.equal(
validateOwnerTransferBody(buildOwnerTransferBody('2060000000000000001'), '2060000000000000001'),
'不能转让给自己'
);
});
test('世系人物绑定选项只接受当前家谱响应中的安全人物 ID', () => {
const normalizeLineageOption = requireFunction('normalizeLineageOption');
assert.deepEqual(normalizeLineageOption({
personId: '2060000000000000001',
name: '李明',
generationName: '德'
}), {
personId: '2060000000000000001',
name: '李明',
generationName: '德'
});
assert.equal(normalizeLineageOption({ personId: Number('2060000000000000001'), name: '李明' }), null);
assert.equal(normalizeLineageOption({ personId: '1', name: '' }), null);
});
test('成员列表展示转义内容并隐藏手机号与内部 ID', () => {
const normalizeMember = requireFunction('normalizeMember');
const renderMemberRow = requireFunction('renderMemberRow');
const html = renderMemberRow(normalizeMember({
memberId: '2060000000000000001',
genealogyId: '2060000000000000002',
appUserId: '2060000000000000003',
appUserNickName: '
明远',
appUserPhone: '19100000000',
lineagePersonId: '2060000000000000004',
lineagePersonName: '李明',
memberName: '李先生',
roleType: 'member',
relationName: '族亲',
inviterUserId: '2060000000000000005',
inviterPhone: '19111111111',
status: '0'
}), {
actorRole: 'member',
currentMemberId: '2060000000000000001'
});
assert.doesNotMatch(html, /