const assert = require('node:assert/strict'); const fs = require('node:fs'); const path = require('node:path'); const test = require('node:test'); const VipPages = require('../public/js/vip-pages.js'); const root = path.resolve(__dirname, '..'); const packageId = '2062179707935264769'; const genealogyId = '2062179707935264770'; const orderId = '2062179707935264771'; function packageFixture(overrides) { return Object.assign({ packageId, packageName: '家谱年度会员', packageType: 'vip', packageDesc: '适合持续维护家谱', price: '99.00', originalPrice: '129.00', durationValue: 1, durationUnit: 'year', genealogyLimit: 3, memberLimit: 500, storageLimitMb: 10240, featureJson: '{"export":true}', sortOrder: 1, status: '0', remark: '年度套餐' }, overrides); } function orderFixture(overrides) { return Object.assign({ orderId, orderNo: 'VIP202607291200001234', packageId, packageName: '家谱年度会员', appUserId: '2062179707935264701', appUserNickName: '叶子', appUserPhone: '19100000000', genealogyId, genealogyNo: 'G20260729001', genealogyName: '叶氏家谱', orderAmount: '99.00', payAmount: '99.00', payType: 'wechat', payStatus: '0', payTime: null, expireTime: null, status: '0', remark: '' }, overrides); } test('VIP 套餐只保留可展示字段并保持金额和长 ID 字符串', () => { assert.deepEqual(VipPages.normalizeVipPackage(packageFixture()), { packageId, packageName: '家谱年度会员', packageType: 'vip', packageDesc: '适合持续维护家谱', price: '99.00', originalPrice: '129.00', durationValue: 1, durationUnit: 'year', genealogyLimit: 3, memberLimit: 500, storageLimitMb: 10240, remark: '年度套餐' }); assert.equal(VipPages.normalizeVipPackage(packageFixture({ packageId: Number.MAX_SAFE_INTEGER + 1 })), null); assert.equal(VipPages.normalizeVipPackage(packageFixture({ packageType: 'unknown' })), null); assert.equal(VipPages.normalizeVipPackage(packageFixture({ durationUnit: 'week' })), null); assert.equal(VipPages.normalizeVipPackage(packageFixture({ price: '-1.00' })), null); assert.equal(VipPages.normalizeVipPackage(packageFixture({ status: '1' })), null); }); test('套餐列表过滤停用或非法记录', () => { assert.equal(VipPages.normalizeVipPackages([ packageFixture(), packageFixture({ packageId: '2062179707935264772', status: '1' }) ]).length, 1); assert.deepEqual(VipPages.normalizeVipPackages({ rows: [packageFixture()] }), []); }); test('家谱选择项只接受响应中的稳定 ID 和名称', () => { assert.deepEqual(VipPages.normalizeGenealogyOption({ genealogyId, genealogyName: '叶氏家谱', surname: '叶', ownerUserId: 'must-drop' }), { genealogyId, genealogyName: '叶氏家谱', surname: '叶' }); assert.equal(VipPages.normalizeGenealogyOption({ genealogyId: Number.MAX_SAFE_INTEGER + 1, genealogyName: '叶氏家谱' }), null); assert.equal(VipPages.normalizeGenealogyOption({ genealogyId, genealogyName: '' }), null); }); test('订单请求只构造 AppVipOrderBody 且页面默认省略支付方式', () => { assert.deepEqual(VipPages.buildVipOrderBody({ packageId: ' ' + packageId + ' ', genealogyId: ' ' + genealogyId + ' ', payType: '', orderId: 'must-drop', appUserId: 'must-drop', payStatus: 'must-drop' }), { packageId, genealogyId }); assert.deepEqual(VipPages.buildVipOrderBody({ packageId, payType: ' wechat ' }), { packageId, payType: 'wechat' }); }); test('订单请求要求安全套餐 ID,并只接受已确认的可选值', () => { assert.equal(VipPages.validateVipOrderBody({ packageId }), ''); assert.equal(VipPages.validateVipOrderBody({ packageId, genealogyId }), ''); assert.match(VipPages.validateVipOrderBody({ packageId: '' }), /套餐/); assert.match(VipPages.validateVipOrderBody({ packageId, genealogyId: 'unsafe' }), /家谱/); assert.match(VipPages.validateVipOrderBody({ packageId, payType: 'alipay' }), /支付方式/); }); test('VIP 订单隐藏账号字段并校验金额和状态', () => { assert.deepEqual(VipPages.normalizeVipOrder(orderFixture()), { orderId, orderNo: 'VIP202607291200001234', packageId, packageName: '家谱年度会员', genealogyId, genealogyNo: 'G20260729001', genealogyName: '叶氏家谱', orderAmount: '99.00', payAmount: '99.00', payType: 'wechat', payStatus: '0', payTime: '', expireTime: '', status: '0', remark: '' }); assert.equal(VipPages.normalizeVipOrder(orderFixture({ orderId: Number.MAX_SAFE_INTEGER + 1 })), null); assert.equal(VipPages.normalizeVipOrder(orderFixture({ payAmount: '-1' })), null); assert.equal(VipPages.normalizeVipOrder(orderFixture({ payStatus: '9' })), null); assert.equal(VipPages.normalizeVipOrder(orderFixture({ status: '9' })), null); }); test('套餐和订单渲染不出现内部账号信息或伪支付操作', () => { const packagesHtml = VipPages.renderVipPackages([packageFixture({ packageName: '<年度会员>' })], packageId); const ordersHtml = VipPages.renderVipOrders([orderFixture({ appUserId: 'secret-user', appUserPhone: 'secret-phone' })]); const combined = packagesHtml + ordersHtml; assert.match(packagesHtml, /<年度会员>/); assert.match(packagesHtml, new RegExp('data-vip-package-id="' + packageId + '"')); assert.match(ordersHtml, /待支付/); assert.doesNotMatch(combined, /secret-user|secret-phone|appUserId|appUserPhone|立即支付|模拟|取消订单|退款/); }); test('套餐渲染接受初始化流程已经规范化的数据', () => { const normalized = VipPages.normalizeVipPackages([packageFixture()]); const html = VipPages.renderVipPackages(normalized, packageId); assert.match(html, /家谱年度会员/); assert.match(html, /data-vip-package-id=/); assert.doesNotMatch(html, /当前没有可用会员套餐/); }); test('会员服务页只从接口结果选择套餐和家谱,不暴露业务 ID 或未开放支付操作', () => { const source = fs.readFileSync(path.join(root, 'profile-services.html'), 'utf8'); assert.doesNotMatch(source, /data-feature-status="pending"/); assert.match(source, /src="public\/js\/vip-pages\.js"/); assert.match(source, /data-vip-package-list/); assert.match(source, /data-vip-selected-package/); assert.match(source, /]+data-vip-genealogy-options/); assert.match(source, /data-vip-order-refresh/); assert.match(source, /data-vip-order-list/); assert.doesNotMatch(source, /]+name="(?:packageId|genealogyId|orderId)"/); assert.doesNotMatch(source, /name="payType"/); assert.doesNotMatch(source, /立即支付|模拟支付|取消订单|退款/); }); test('会员服务运行时并行读取套餐、我的家谱和订单,并在创建后重新读取核验', () => { const source = fs.readFileSync(path.join(root, 'public/js/vip-pages.js'), 'utf8'); assert.match(source, /Promise\.all\(\[api\.vipPackages\(\), api\.genealogiesMine\(\), api\.vipOrders\(\)\]\)/); assert.match(source, /created = normalizeVipOrder\(await api\.createVipOrder\(body\)\)/); assert.match(source, /refreshed = normalizeVipOrders\(await api\.vipOrders\(\)\)/); assert.match(source, /item\.orderId === created\.orderId/); });