/** * 历史收益全量结转至钱包余额专项测试 * 覆盖范围: * 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); }