141 行
7.2 KiB
JavaScript
141 行
7.2 KiB
JavaScript
/**
|
||
* 方案 B 核心业务规范自动化断言测试:
|
||
* 规则:收益进入哪个钱包,提现就走哪个钱包提现(统一为「钱包余额 money」)
|
||
* 覆盖范围:
|
||
* 1. addons/shopro/service/Withdraw.php 服务层 apply 默认 money 提现、checkApply 校验 money、驳回/失败退还 money
|
||
* 2. uniapp/pages/pay/withdraw.vue 前端默认 money 提现与 payload 校验
|
||
* 3. application/admin/model/shopro/Withdraw.php 与 index.html 专属钱包余额标签与获取器
|
||
* 4. application/admin/model/shopro/user/WalletLog.php 字典对齐
|
||
* 5. 扣款、费率、超额拦截、驳回退回、失败退回与数据校正守恒算法模拟断言
|
||
*/
|
||
|
||
const fs = require('fs');
|
||
const path = require('path');
|
||
const assert = require('assert');
|
||
|
||
let passCount = 0;
|
||
let failCount = 0;
|
||
|
||
function testAssert(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(' 方案 B(收益与提现统一走钱包余额 money)全链路测试');
|
||
console.log('========================================================\n');
|
||
|
||
// 1. 服务层 Withdraw.php 验证
|
||
console.log('--- 1. addons/shopro/service/Withdraw.php 服务层闭环验证 ---');
|
||
const withdrawServicePath = path.resolve(__dirname, '../../addons/shopro/service/Withdraw.php');
|
||
testAssert(fs.existsSync(withdrawServicePath), 'Withdraw.php 服务文件存在');
|
||
if (fs.existsSync(withdrawServicePath)) {
|
||
const content = fs.readFileSync(withdrawServicePath, 'utf-8');
|
||
testAssert(content.includes("$walletType = $params['wallet_type'] ?? 'money';"), '提现钱包默认缺省值为 money(钱包余额)');
|
||
testAssert(content.includes("WalletService::change($this->user, 'money'"), 'apply 提现成功扣除 money 钱包');
|
||
testAssert(content.includes("ShoproException('钱包可用余额不足')"), 'checkApply 严格校验钱包余额不足并提示明确异常');
|
||
testAssert(content.includes("bccomp((string)($this->user->money ?? 0)"), '使用 bccomp 高精度比较用户余额');
|
||
testAssert(content.includes("$isMoney = (isset($info['wallet_type']) && $info['wallet_type'] === 'money')"), '正确识别 isMoney 提现类型');
|
||
testAssert(content.includes("'提现驳回退还余额'"), 'handleRefuse 驳回退款精准退回用户钱包余额');
|
||
testAssert(content.includes("'提现失败退还余额'"), 'handleFail 失败退款精准退回用户钱包余额');
|
||
}
|
||
|
||
// 2. 前端提现页面 withdraw.vue 验证
|
||
console.log('\n--- 2. uniapp/pages/pay/withdraw.vue 前端绑定与参数验证 ---');
|
||
const withdrawVuePath = path.resolve(__dirname, '../../uniapp/pages/pay/withdraw.vue');
|
||
testAssert(fs.existsSync(withdrawVuePath), 'withdraw.vue 文件存在');
|
||
if (fs.existsSync(withdrawVuePath)) {
|
||
const content = fs.readFileSync(withdrawVuePath, 'utf-8');
|
||
testAssert(content.includes("walletType: 'money'"), 'state.walletType 默认初始化为 money');
|
||
testAssert(content.includes("userInfo.money || '0.00'"), '顶部展示用户钱包余额 userInfo.money');
|
||
testAssert(content.includes("payload.wallet_type = 'money'"), '提现提交显式指定 wallet_type: money');
|
||
}
|
||
|
||
// 3. 后台展示与字典验证
|
||
console.log('\n--- 3. 后台模型与视图资产标记验证 ---');
|
||
const withdrawModelPath = path.resolve(__dirname, '../../application/admin/model/shopro/Withdraw.php');
|
||
testAssert(fs.existsSync(withdrawModelPath), 'Withdraw.php 模型存在');
|
||
if (fs.existsSync(withdrawModelPath)) {
|
||
const content = fs.readFileSync(withdrawModelPath, 'utf-8');
|
||
testAssert(content.includes("getIsMoneyWithdrawAttr"), '提供 getIsMoneyWithdrawAttr 获取器');
|
||
testAssert(content.includes("$display['提现资产'] = '钱包余额'"), '注入 提现资产: 钱包余额');
|
||
}
|
||
|
||
const walletLogPath = path.resolve(__dirname, '../../application/admin/model/shopro/user/WalletLog.php');
|
||
testAssert(fs.existsSync(walletLogPath), 'WalletLog.php 模型存在');
|
||
if (fs.existsSync(walletLogPath)) {
|
||
const content = fs.readFileSync(walletLogPath, 'utf-8');
|
||
testAssert(content.includes("'withdraw' => '申请提现'"), 'money 注册 withdraw 字典');
|
||
testAssert(content.includes("'withdraw_error' => '提现失败-返还余额'"), 'money 注册 withdraw_error 字典');
|
||
}
|
||
|
||
// 4. 算法与资金守恒模拟
|
||
console.log('\n--- 4. 业务场景算法与资金守恒断言 ---');
|
||
const testUser = {
|
||
money: 1614.65,
|
||
commission: 806.00
|
||
};
|
||
|
||
const chargeRate = 0.06; // 6% 手续费
|
||
|
||
function applyWithdrawMoney(user, amount) {
|
||
const charge = Math.round(amount * chargeRate * 100) / 100;
|
||
const totalDeduct = Math.round((amount + charge) * 100) / 100;
|
||
if (user.money < totalDeduct) {
|
||
throw new Error('钱包可用余额不足');
|
||
}
|
||
user.money = Math.round((user.money - totalDeduct) * 100) / 100;
|
||
return { amount, charge, totalDeduct, wallet_type: 'money' };
|
||
}
|
||
|
||
function refundWithdrawMoney(user, record) {
|
||
user.money = Math.round((user.money + record.totalDeduct) * 100) / 100;
|
||
}
|
||
|
||
// 场景 1:正常提现 1000 元
|
||
const snapCommission = testUser.commission;
|
||
const order1 = applyWithdrawMoney(testUser, 1000.00);
|
||
testAssert(order1.amount === 1000.00, '提现本金 1000.00 元');
|
||
testAssert(order1.charge === 60.00, '6% 手续费 60.00 元');
|
||
testAssert(testUser.money === 554.65, '钱包余额由 1614.65 扣减至 554.65');
|
||
testAssert(testUser.commission === snapCommission, '佣金钱包未受任何触碰,保持 806.00');
|
||
|
||
// 场景 2:超额提现拦截
|
||
let interceptPassed = false;
|
||
try {
|
||
applyWithdrawMoney(testUser, 600.00); // 需扣 636,剩余 554.65 不足
|
||
} catch (e) {
|
||
interceptPassed = (e.message === '钱包可用余额不足');
|
||
}
|
||
testAssert(interceptPassed, '余额不足拦截有效,抛出【钱包可用余额不足】');
|
||
|
||
// 场景 3:审核驳回退回
|
||
refundWithdrawMoney(testUser, order1);
|
||
testAssert(testUser.money === 1614.65, '提现驳回后钱包余额原路全额退还至 1614.65');
|
||
testAssert(testUser.commission === snapCommission, '佣金钱包继续保持 806.00 不受影响');
|
||
|
||
// 场景 4:针对订单 W202601240278270146002000 的数据校正逻辑断言
|
||
console.log('\n--- 5. 提现单 W202601240278270146002000 数据校正模拟 ---');
|
||
const user20BeforeFix = {
|
||
money: 1614.65,
|
||
commission: 806.00 // 误扣了 1060 元后的佣金
|
||
};
|
||
// 校正:扣减 money 1060,返还 commission 1060
|
||
const fixDeduct = 1060.00;
|
||
user20BeforeFix.money = Math.round((user20BeforeFix.money - fixDeduct) * 100) / 100;
|
||
user20BeforeFix.commission = Math.round((user20BeforeFix.commission + fixDeduct) * 100) / 100;
|
||
|
||
testAssert(user20BeforeFix.money === 554.65, '校正后用户 20 钱包余额为 554.65(扣除了提现的 1060 元)');
|
||
testAssert(user20BeforeFix.commission === 1866.00, '校正后用户 20 佣金返还为 1866.00(恢复了历史存量佣金)');
|
||
|
||
console.log(`\n========================================================`);
|
||
console.log(` 测试结果: 通过 ${passCount} 项, 失败 ${failCount} 项`);
|
||
console.log(`========================================================\n`);
|
||
|
||
assert.strictEqual(failCount, 0, '所有测试必须全部通过');
|