const assert = require('node:assert/strict'); const fs = require('node:fs'); const path = require('node:path'); const test = require('node:test'); const InvitePages = require('../public/js/family-invite-pages.js'); const ReminderPages = require('../public/js/profile-reminder-pages.js'); const LifecyclePages = require('../public/js/genealogy-lifecycle-pages.js'); const DocumentPages = require('../public/js/person-document-pages.js'); const EarningPages = require('../public/js/earning-pages.js'); const root = path.resolve(__dirname, '..'); const genealogyId = '2062179707935264701'; test('邀请列表保留长 ID、转义服务端文本并只允许撤销待使用邀请', () => { const html = InvitePages.renderInvitations([{ inviteId: '2062179707935264702', genealogyId, genealogyName: '<叶氏家谱>', lineagePersonName: '叶一', status: 'ACTIVE', expiresAt: '2026-09-01T00:00:00+08:00' }]); assert.match(html, /<叶氏家谱>/); assert.match(html, /data-invite-revoke="2062179707935264702"/); assert.doesNotMatch(InvitePages.renderInvitations([{ inviteId: '2062179707935264702', genealogyId, genealogyName: '叶氏家谱', status: 'REDEEMED' }]), /data-invite-revoke/); }); test('资料提醒只接受具备人物、姓名和缺项的记录', () => { assert.deepEqual(ReminderPages.normalizeReminder({ personId: '2062179707935264703', displayName: '叶一', missingField: 'birthDate', missingFieldLabel: '出生日期', handlingStatus: 'PENDING' }), { personId: '2062179707935264703', displayName: '叶一', label: '出生日期', handlingStatus: 'PENDING', updatedAt: '' }); assert.equal(ReminderPages.normalizeReminder({ personId: 'x', displayName: '叶一', missingField: 'birthDate' }), null); }); test('永久删除能力将稳定阻断码转换为用户可读原因', () => { assert.equal(LifecyclePages.capabilityMessage({ canDeletePermanently: false, disabledReasons: ['NOT_OWNER'] }), '只有谱主可以永久删除家谱'); assert.match(LifecyclePages.capabilityMessage({ canDeletePermanently: true, verifiedMobileMasked: '138****0000', disabledReasons: [] }), /138\*\*\*\*0000/); }); test('重要证件请求严格校验人物、类型、标题和脱敏字段', () => { const body = DocumentPages.buildDocumentBody({ lineagePersonId: '2062179707935264703', documentType: 'id_card', documentTitle: '身份证', maskedIdentifier: '310***********1234', description: '仅供家族档案使用' }); assert.equal(DocumentPages.validateDocumentBody(body), ''); assert.match(DocumentPages.validateDocumentBody({ ...body, documentTitle: '' }), /标题/); assert.match(DocumentPages.renderDocuments([{ documentId: '2062179707935264704', lineagePersonId: '2062179707935264703', lineagePersonName: '<叶一>', documentType: 'id_card', documentTypeLabel: '身份证', documentTitle: '身份证', resources: [{ resourceId: '2062179707935264705', usageType: 'FRONT' }], contentProtected: true, contentUnlocked: false, canEdit: true, canDelete: true }]), /<叶一>|data-document-resource/); }); test('提现请求使用后端幂等合同并校验余额和收款码', () => { const body = EarningPages.buildWithdrawalBody({ amount: '88.50', payoutQrOssId: '2062179707935264706', payoutAccountName: '叶一' }, 'pc-request-1'); assert.deepEqual(body, { requestId: 'pc-request-1', amount: '88.50', payoutQrOssId: '2062179707935264706', payoutAccountName: '叶一' }); assert.equal(EarningPages.validateWithdrawalBody(body, '100.00'), ''); assert.match(EarningPages.validateWithdrawalBody({ ...body, amount: '100.01' }, '100.00'), /可用余额/); assert.match(EarningPages.validateWithdrawalBody({ ...body, amount: '9999999999999999.99' }, '9999999999999999.98'), /可用余额/); }); test('新增 PC 闭环页面加载统一请求层和对应运行时', () => { [ ['profile-invite.html', 'family-invite-pages.js'], ['profile-data-reminders.html', 'profile-reminder-pages.js'], ['profile-family-settings.html', 'genealogy-lifecycle-pages.js'], ['profile-documents.html', 'person-document-pages.js'], ['profile-earnings.html', 'earning-pages.js'] ].forEach(([file, runtime]) => { const source = fs.readFileSync(path.join(root, file), 'utf8'); assert.match(source, /utils\/ApiClient\.js/, `${file} 未加载统一请求层`); assert.match(source, new RegExp(runtime.replace('.', '\\.')), `${file} 未加载 ${runtime}`); assert.doesNotMatch(source, /data-feature-status="pending"/); }); });