/** * 提现金额 100 整数倍规则全链路验证测试 * * 核心规则要求: * 1. 提现金额必须为整数且为 100 的整数倍(如 100、200、1200 等)。 * 2. 举例场景:余额有 115 元,只能提现 100 元;余额有 1260 元,只能提现 1200 元。 * 3. 余额不足 100 元时,点击全部提现提示“余额满100元方可申请提现”,输入框置空。 * 4. 前端提现输入框占位符、提现说明 Banner、提现规则列表同步展示 100 整数倍规则。 * 5. 前端确认提交时拦截非 100 整数倍,后端 checkApply 抛出对应异常。 */ 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(' 提现金额 100 整数倍限制规则全链路验证测试'); console.log('========================================================\n'); // ---------------------------------------------------- // 1. 服务端 addons/shopro/service/Withdraw.php 校验规则 // ---------------------------------------------------- console.log('--- 1. 服务端 Withdraw.php 校验规则断言 ---'); const withdrawServicePath = path.resolve(__dirname, '../../addons/shopro/service/Withdraw.php'); testAssert(fs.existsSync(withdrawServicePath), 'Withdraw.php 服务层文件存在'); if (fs.existsSync(withdrawServicePath)) { const phpContent = fs.readFileSync(withdrawServicePath, 'utf-8'); testAssert( phpContent.includes('!is_numeric($money) || floatval($money) <= 0'), 'Withdraw.php checkApply 校验 is_numeric 与 floatval($money) <= 0' ); testAssert( phpContent.includes('floor($floatMoney) != $floatMoney || (intval($floatMoney) % 100 !== 0)'), 'Withdraw.php checkApply 校验必须为整数且为 100 的整数倍' ); testAssert( phpContent.includes("throw new ShoproException('提现金额必须为100的整数倍')"), 'Withdraw.php 非 100 整数倍时抛出 ShoproException(\'提现金额必须为100的整数倍\')' ); } // ---------------------------------------------------- // 2. 客户端 uniapp/pages/pay/withdraw.vue 界面与逻辑 // ---------------------------------------------------- console.log('\n--- 2. 客户端 withdraw.vue 界面与逻辑断言 ---'); const withdrawVuePath = path.resolve(__dirname, '../../uniapp/pages/pay/withdraw.vue'); testAssert(fs.existsSync(withdrawVuePath), 'withdraw.vue 前端源码文件存在'); if (fs.existsSync(withdrawVuePath)) { const vueContent = fs.readFileSync(withdrawVuePath, 'utf-8'); testAssert( vueContent.includes("placeholder=\"state.walletType === 'madow' ? '请输入提现金额' : '请输入100的整数倍金额(如100、1200)'\""), '输入框占位符提示请输入100的整数倍金额' ); testAssert( vueContent.includes("提现金额须为100的整数倍(如100、200、1200等)"), '提现说明 Banner 提示提现金额须为100的整数倍' ); testAssert( vueContent.includes("list.push('单次提现金额须为 100 元的整数倍(如 100、200、1200 等);')"), 'filterRules 规则列表包含 100 元整数倍规则' ); testAssert( vueContent.includes("Math.floor(total / 100) * 100"), 'onWithdrawAllMoney / onWithdrawAllCommission 采用 Math.floor(total / 100) * 100 向下取整' ); testAssert( vueContent.includes("sheep.$helper.toast('余额满100元方可申请提现')"), 'onWithdrawAllMoney 不足 100 元时提示余额满100元方可申请提现' ); testAssert( vueContent.includes("sheep.$helper.toast('佣金满100元方可申请提现')"), 'onWithdrawAllCommission 不足 100 元时提示佣金满100元方可申请提现' ); testAssert( vueContent.includes("!Number.isInteger(moneyVal) || moneyVal % 100 !== 0"), 'onConfirm 提现提交前前端强校验 100 的整数倍' ); testAssert( vueContent.includes("sheep.$helper.toast('提现金额须为100的整数倍')"), 'onConfirm 非 100 整数倍时前端友好 toast 提示' ); } // ---------------------------------------------------- // 3. 小程序开发包编译产物一致性验证 // ---------------------------------------------------- console.log('\n--- 3. 小程序编译包一致性断言 ---'); const distDevWithdrawJsPath = path.resolve(__dirname, '../../uniapp/unpackage/dist/dev/mp-weixin/pages/pay/withdraw.js'); testAssert(fs.existsSync(distDevWithdrawJsPath), 'dev 编译产物 withdraw.js 存在'); if (fs.existsSync(distDevWithdrawJsPath)) { const distContent = fs.readFileSync(distDevWithdrawJsPath, 'utf-8'); testAssert( distContent.includes("Math.floor(total / 100) * 100"), 'dev 编译产物已同步 Math.floor(total / 100) * 100' ); testAssert( distContent.includes("!Number.isInteger(moneyVal) || moneyVal % 100 !== 0"), 'dev 编译产物已同步 onConfirm 校验' ); } // ---------------------------------------------------- // 4. 业务场景算法断言(覆盖用户指定举例) // ---------------------------------------------------- console.log('\n--- 4. 核心算法与用户场景模拟测试 ---'); // 模拟前端 onWithdrawAllMoney 计算函数 function calculateWithdrawAll(balance) { const total = parseFloat(balance || 0); const amount = Math.floor(total / 100) * 100; if (amount <= 0) { return { success: false, amount: '', message: '余额满100元方可申请提现' }; } return { success: true, amount: String(amount), message: '操作成功' }; } // 模拟服务端 checkApply 提现金额校验函数 function validateWithdrawMoney(moneyInput, userBalance) { if (!isFinite(moneyInput) || parseFloat(moneyInput) <= 0) { return { valid: false, message: '请输入正确的提现金额' }; } const floatMoney = parseFloat(moneyInput); if (Math.floor(floatMoney) !== floatMoney || (Math.floor(floatMoney) % 100 !== 0)) { return { valid: false, message: '提现金额必须为100的整数倍' }; } if (floatMoney > userBalance) { return { valid: false, message: '钱包可用余额不足' }; } return { valid: true, message: '验证通过' }; } // 场景 1:用户余额 115 元 -> 点击「全部提现」自动填入 100 const res115 = calculateWithdrawAll(115); testAssert(res115.success === true && res115.amount === '100', '用户场景1:余额 115 元点击「全部提现」,填入 100 元'); // 场景 2:用户余额 1260 元 -> 点击「全部提现」自动填入 1200 const res1260 = calculateWithdrawAll(1260); testAssert(res1260.success === true && res1260.amount === '1200', '用户场景2:余额 1260 元点击「全部提现」,填入 1200 元'); // 场景 3:用户余额 99 元 -> 点击「全部提现」提示余额满100元方可申请提现 const res99 = calculateWithdrawAll(99); testAssert(res99.success === false && res99.amount === '' && res99.message === '余额满100元方可申请提现', '用户场景3:余额 99 元点击「全部提现」,提示满100元方可提现并置空'); // 场景 4:用户余额 100 元 -> 点击「全部提现」填入 100 元 const res100 = calculateWithdrawAll(100); testAssert(res100.success === true && res100.amount === '100', '用户场景4:余额 100 元点击「全部提现」,填入 100 元'); // 场景 5:用户余额 2000.88 元 -> 点击「全部提现」填入 2000 元 const res2000 = calculateWithdrawAll(2000.88); testAssert(res2000.success === true && res2000.amount === '2000', '用户场景5:余额 2000.88 元点击「全部提现」,填入 2000 元'); // 校验服务端验证用例 const val100 = validateWithdrawMoney(100, 115); testAssert(val100.valid === true, '校验用例:提现 100 元(余额 115 元)服务端校验通过'); const val1200 = validateWithdrawMoney(1200, 1260); testAssert(val1200.valid === true, '校验用例:提现 1200 元(余额 1260 元)服务端校验通过'); const val115 = validateWithdrawMoney(115, 115); testAssert(val115.valid === false && val115.message === '提现金额必须为100的整数倍', '校验用例:直接提交 115 元被服务端拦截(必须为100的整数倍)'); const val1260 = validateWithdrawMoney(1260, 1260); testAssert(val1260.valid === false && val1260.message === '提现金额必须为100的整数倍', '校验用例:直接提交 1260 元被服务端拦截(必须为100的整数倍)'); const valDecimal = validateWithdrawMoney(100.5, 200); testAssert(valDecimal.valid === false && valDecimal.message === '提现金额必须为100的整数倍', '校验用例:小数 100.5 元被服务端拦截(必须为100的整数倍)'); const valZero = validateWithdrawMoney(0, 500); testAssert(valZero.valid === false && valZero.message === '请输入正确的提现金额', '校验用例:金额 0 元被拦截'); const valNegative = validateWithdrawMoney(-100, 500); testAssert(valNegative.valid === false && valNegative.message === '请输入正确的提现金额', '校验用例:金额 -100 元被拦截'); const valExceed = validateWithdrawMoney(200, 150); testAssert(valExceed.valid === false && valExceed.message === '钱包可用余额不足', '校验用例:金额 200 元(余额 150 元)超额拦截'); console.log('\n========================================================'); console.log(` 测试结果汇总:共 ${passCount + failCount} 项测试,通过: ${passCount},失败: ${failCount}`); console.log('========================================================'); if (failCount > 0) { process.exit(1); } else { process.exit(0); }