文件
Henglianshutong/tests/partner/verify_wallet_migration_spec.js

194 行
9.0 KiB
JavaScript

/**
* 历史收益全量结转至钱包余额专项测试
* 覆盖范围:
* 1. WalletMigrationService 业务服务契约与原子性逻辑
* 2. PartnerTransferCommission 控制台命令
* 3. Shopro.php 注册命令配置
* 4. transfer_all_commission_to_money.php 生产独立运维脚本
* 5. Reward.php 后台接口与防误触确认校验
* 6. 算法行为模拟:多用户结转、资金守恒、零余额跳过、幂等重入、单用户异常容灾隔离
*/
const fs = require('fs');
const path = require('path');
let passCount = 0;
let failCount = 0;
function assert(condition, message) {
if (condition) {
console.log(` \x1b[32m[PASS]\x1b[0m ${message}`);
passCount++;
} else {
console.error(` \x1b[31m[FAIL]\x1b[0m ${message}`);
failCount++;
}
}
console.log('========================================================');
console.log(' 历史收益全量结转至钱包余额专项测试');
console.log('========================================================\n');
// ----------------------------------------------------
// 1. WalletMigrationService.php 验证
// ----------------------------------------------------
console.log('--- 1. WalletMigrationService.php 业务规范验证 ---');
const migrationServicePath = path.resolve(__dirname, '../../addons/shopro/service/partner/WalletMigrationService.php');
assert(fs.existsSync(migrationServicePath), 'WalletMigrationService.php 文件存在');
if (fs.existsSync(migrationServicePath)) {
const content = fs.readFileSync(migrationServicePath, 'utf-8');
assert(content.includes('public function preview()'), '包含 preview() 试算预览方法');
assert(content.includes('public function transferAll('), '包含 transferAll() 全量结转执行方法');
assert(content.includes('Db::transaction(function ()'), '结转过程使用数据库事务');
assert(content.includes('lock(true)'), '读取用户使用排他行锁 lock(true) 防并发脏读');
assert(content.includes("Wallet::change($user, 'commission', -$amount, 'transfer_to_money'"), '调用 Wallet::change 扣减佣金钱包');
assert(content.includes("Wallet::change($user, 'money', $amount, 'transfer_by_commission'"), '调用 Wallet::change 增加钱包余额');
assert(content.includes('bcadd('), '资金累计采用高精度 bcadd 避免浮点误差');
}
// ----------------------------------------------------
// 2. PartnerTransferCommission.php 控制台命令验证
// ----------------------------------------------------
console.log('\n--- 2. PartnerTransferCommission.php 控制台命令验证 ---');
const consoleCmdPath = path.resolve(__dirname, '../../addons/shopro/console/PartnerTransferCommission.php');
assert(fs.existsSync(consoleCmdPath), 'PartnerTransferCommission.php 文件存在');
if (fs.existsSync(consoleCmdPath)) {
const content = fs.readFileSync(consoleCmdPath, 'utf-8');
assert(content.includes("'shopro:partner:transfer-commission'"), '命令名称定义为 shopro:partner:transfer-commission');
assert(content.includes("'dry-run'"), '命令支持 --dry-run 试算选项');
assert(content.includes("'force'"), '命令支持 --force 强制执行选项');
assert(content.includes('WalletMigrationService'), '命令调用 WalletMigrationService 服务');
}
// ----------------------------------------------------
// 3. Shopro.php 自动命令注册验证
// ----------------------------------------------------
console.log('\n--- 3. Shopro.php 注册命令验证 ---');
const shoproPath = path.resolve(__dirname, '../../addons/shopro/Shopro.php');
assert(fs.existsSync(shoproPath), 'Shopro.php 文件存在');
if (fs.existsSync(shoproPath)) {
const content = fs.readFileSync(shoproPath, 'utf-8');
assert(content.includes("'addons\\shopro\\console\\PartnerTransferCommission'"), 'Shopro.php addDefaultCommands 已注册 PartnerTransferCommission');
}
// ----------------------------------------------------
// 4. transfer_all_commission_to_money.php 生产脚本验证
// ----------------------------------------------------
console.log('\n--- 4. transfer_all_commission_to_money.php 运维脚本验证 ---');
const scriptPath = path.resolve(__dirname, '../../scripts/transfer_all_commission_to_money.php');
assert(fs.existsSync(scriptPath), 'transfer_all_commission_to_money.php 脚本存在');
if (fs.existsSync(scriptPath)) {
const content = fs.readFileSync(scriptPath, 'utf-8');
assert(content.includes('WalletMigrationService'), '脚本引入 WalletMigrationService 执行');
assert(content.includes('--dry-run'), '脚本支持 --dry-run');
assert(content.includes('--force'), '脚本安全防御支持 --force');
}
// ----------------------------------------------------
// 5. Reward.php 后台接口验证
// ----------------------------------------------------
console.log('\n--- 5. Reward.php 后台接口验证 ---');
const rewardPath = path.resolve(__dirname, '../../application/admin/controller/shopro/partner/Reward.php');
assert(fs.existsSync(rewardPath), 'Reward.php 文件存在');
if (fs.existsSync(rewardPath)) {
const content = fs.readFileSync(rewardPath, 'utf-8');
assert(content.includes('public function transferAllCommissionPreview()'), '包含 transferAllCommissionPreview 后台预览接口');
assert(content.includes('public function transferAllCommission()'), '包含 transferAllCommission 后台执行接口');
assert(content.includes('confirm !== 1'), '正式结转接口必须传递 confirm=1 进行二次安全防御');
}
// ----------------------------------------------------
// 6. 算法行为模拟断言
// ----------------------------------------------------
console.log('\n--- 6. 资金结转算法与守恒性模拟断言 ---');
function mockTransferService(users) {
let successCount = 0;
let failCount = 0;
let totalTransferred = 0;
const logs = [];
users.forEach(u => {
if (u.simulateError) {
failCount++;
return;
}
const comm = parseFloat(u.commission);
if (comm <= 0) {
return; // 跳过
}
const money = parseFloat(u.money);
// 执行原子互转
u.commission = '0.00';
u.money = (money + comm).toFixed(2);
totalTransferred += comm;
successCount++;
logs.push({
user_id: u.id,
type: 'commission',
amount: -comm,
event: 'transfer_to_money',
});
logs.push({
user_id: u.id,
type: 'money',
amount: comm,
event: 'transfer_by_commission',
});
});
return {
successCount,
failCount,
totalTransferred: totalTransferred.toFixed(2),
logs,
};
}
// 测试数据集
const initialUsers = [
{ id: 1, nickname: '张三', money: '100.00', commission: '250.50' },
{ id: 2, nickname: '李四', money: '0.00', commission: '398.00' },
{ id: 3, nickname: '王五', money: '50.00', commission: '0.00' }, // 佣金为0
{ id: 4, nickname: '赵六', money: '80.00', commission: '120.00', simulateError: true }, // 模拟异常
];
// 计算初始总资金
const initialTotalAssets = initialUsers.reduce((sum, u) => {
return sum + parseFloat(u.money) + parseFloat(u.commission);
}, 0);
const result = mockTransferService(initialUsers);
assert(result.successCount === 2, '成功结转 2 位有佣金的会员');
assert(result.failCount === 1, '异常会员被安全隔离统计为 1 笔失败');
assert(result.totalTransferred === '648.50', '结转总金额精确匹配 250.50 + 398.00 = 648.50');
// 校验各个用户结转后的状态
assert(initialUsers[0].commission === '0.00', '张三佣金归零');
assert(initialUsers[0].money === '350.50', '张三钱包余额从 100 增至 350.50');
assert(initialUsers[1].commission === '0.00', '李四佣金归零');
assert(initialUsers[1].money === '398.00', '李四钱包余额从 0 增至 398.00');
assert(initialUsers[2].commission === '0.00' && initialUsers[2].money === '50.00', '王五佣金本为0,保持不变');
assert(initialUsers[3].commission === '120.00' && initialUsers[3].money === '80.00', '赵六发生异常,资金状态未被污染');
// 资金守恒定理断言:结转前后总资产必须完全相等
const finalTotalAssets = initialUsers.reduce((sum, u) => {
return sum + parseFloat(u.money) + parseFloat(u.commission);
}, 0);
assert(Math.abs(initialTotalAssets - finalTotalAssets) < 0.0001, '资金守恒断言:结转前后系统总资产完全守恒');
// 幂等性测试断言:再次运行
const result2 = mockTransferService(initialUsers.filter(u => !u.simulateError));
assert(result2.successCount === 0, '幂等性断言:再次运行成功数为 0');
assert(result2.totalTransferred === '0.00', '幂等性断言:再次运行结转金额为 0.00');
console.log('\n========================================================');
console.log(`测试执行结果: 通过 ${passCount} 项, 失败 ${failCount} 项`);
console.log('========================================================');
if (failCount > 0) {
process.exit(1);
}