Files
jiapu/tests/vip-pages.test.js
T
2026-08-03 16:49:38 +08:00

273 lines
9.8 KiB
JavaScript

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);
assert.deepEqual(VipPages.normalizeGenealogyOptions([
{ genealogyId, genealogyName: '叶氏家谱', surname: '叶' },
{ genealogyId: 'unsafe', genealogyName: '无效家谱' }
]), [{ genealogyId, genealogyName: '叶氏家谱', surname: '叶' }]);
assert.equal(VipPages.resolveContextGenealogyId([
{ genealogyId, genealogyName: '叶氏家谱', surname: '叶' }
], genealogyId), genealogyId);
assert.equal(VipPages.resolveContextGenealogyId([
{ genealogyId, genealogyName: '叶氏家谱', surname: '叶' }
], 'unsafe'), '');
});
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, /&lt;年度会员&gt;/);
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, /<select[^>]+data-vip-genealogy-options/);
assert.match(source, /data-vip-order-refresh/);
assert.match(source, /data-vip-order-list/);
assert.match(source, /data-vip-order-form novalidate/);
assert.match(source, /data-vip-order-status[^>]*role="status"/);
assert.match(source, /data-api-state="loading"/);
assert.doesNotMatch(source, /<input[^>]+name="(?:packageId|genealogyId|orderId)"/);
assert.doesNotMatch(source, /name="payType"/);
assert.doesNotMatch(source, /立即支付|模拟支付|取消订单|退款/);
assert.ok(source.indexOf('src="public/js/profile-common.js"') < source.indexOf('src="public/js/vip-pages.js"'));
});
test('会员服务页补齐桌面端设置与协议入口,不伪造账户接口', () => {
const source = fs.readFileSync(path.join(root, 'profile-services.html'), 'utf8');
assert.match(source, /平台设置与协议/);
assert.match(source, /href="about\.html"/);
assert.match(source, /href="privacy\.html"/);
assert.match(source, /href="children-privacy\.html"/);
assert.match(source, /href="terms\.html"/);
assert.match(source, /个性化推荐/);
assert.match(source, /当前 PC 接口未提供推荐偏好的读取或保存/);
assert.doesNotMatch(source, /data-setting-form|data-withdraw-form|data-share-reward/);
assert.doesNotMatch(source, /data-recommendation-toggle|name="recommendation"/);
});
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/);
});
test('会员服务将加载、空数据、提交校验与请求失败写入统一状态组件', () => {
const source = fs.readFileSync(path.join(root, 'public/js/vip-pages.js'), 'utf8');
assert.match(source, /function setApiState\(target, type, message\)/);
assert.match(source, /root\.ProfileUI && root\.ProfileUI\.setApiState/);
assert.match(source, /setListState\(packageList, 'loading', '正在读取会员套餐…'\)/);
assert.match(source, /setListState\(orderList, 'empty', '当前没有会员订单。'\)/);
assert.match(source, /setStatus\('error', validation\)/);
assert.match(source, /setPendingButton\(submit, true, '正在创建订单…'\)/);
assert.match(source, /root\.ProfileUI && typeof root\.ProfileUI\.getGenealogyId === 'function'/);
assert.match(source, /已关联当前家谱,请选择会员套餐/);
});