文件
Henglianshutong/tests/partner/verify_withdraw_switch_spec.js

206 行
11 KiB
JavaScript

/**
* 后台提现开关与端到端提现显隐控制专项测试
* 覆盖范围:
* 1. application/admin/view/shopro/config/index.html 后台「开启提现」视图控件
* 2. application/admin/controller/shopro/Config.php 提现开关参数持久化
* 3. application/admin/model/shopro/Config.php 提现开关配置项自愈与向下兼容
* 4. addons/shopro/service/Withdraw.php 提现开关读取与 checkApply 拦截
* 5. addons/shopro/controller/Index.php 全局平台配置下发 withdraw_status
* 6. uniapp/pages/user/wallet/money.vue 钱包页充值改提现、开关判断与按钮显隐
* 7. uniapp/pages/pay/withdraw.vue 提现申请页前置校验与安全防御
* 8. 提现开启状态算法判定矩阵与异常拦截模拟
*/
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. 后台视图配置 index.html 验证
// ----------------------------------------------------
console.log('--- 1. 后台提现配置视图 (index.html) 验证 ---');
const adminConfigHtmlPath = path.resolve(__dirname, '../../application/admin/view/shopro/config/index.html');
assert(fs.existsSync(adminConfigHtmlPath), 'config/index.html 文件存在');
if (fs.existsSync(adminConfigHtmlPath)) {
const content = fs.readFileSync(adminConfigHtmlPath, 'utf-8');
assert(content.includes('form.model.withdraw'), '包含提现配置表单区域');
assert(content.includes('label="开启提现"'), '包含「开启提现」表单项标签');
assert(content.includes('v-model="form.model.withdraw.status"'), '开启提现绑定 form.model.withdraw.status');
assert(content.includes(':active-value="1"') && content.includes(':inactive-value="0"'), 'switch 开关 active 值为 1,inactive 值为 0');
assert(content.includes('关闭提现后,用户端将隐藏提现入口且禁止提现操作'), '包含明确的后台辅助说明文案');
}
// ----------------------------------------------------
// 2. 后台配置控制器 Config.php 验证
// ----------------------------------------------------
console.log('\n--- 2. 后台配置控制器 (Config.php) 验证 ---');
const adminConfigPhpPath = path.resolve(__dirname, '../../application/admin/controller/shopro/Config.php');
assert(fs.existsSync(adminConfigPhpPath), 'Config.php 文件存在');
if (fs.existsSync(adminConfigPhpPath)) {
const content = fs.readFileSync(adminConfigPhpPath, 'utf-8');
assert(content.includes("public function rechargeWithdraw()"), '包含 rechargeWithdraw() 配置方法');
assert(content.includes("isset($params['withdraw']['status'])") || content.includes("$params['withdraw']['status']"), '处理了 withdraw.status 的入参保存');
assert(content.includes("intval($params['withdraw']['status'])"), 'withdraw.status 转为整型保证数据严密');
}
// ----------------------------------------------------
// 3. 模型层 Config.php 验证
// ----------------------------------------------------
console.log('\n--- 3. 配置模型 (app/admin/model/shopro/Config.php) 验证 ---');
const modelConfigPhpPath = path.resolve(__dirname, '../../application/admin/model/shopro/Config.php');
assert(fs.existsSync(modelConfigPhpPath), 'Config.php 模型文件存在');
if (fs.existsSync(modelConfigPhpPath)) {
const content = fs.readFileSync(modelConfigPhpPath, 'utf-8');
assert(content.includes("'shop.recharge_withdraw.withdraw.status'"), 'setConfigField 支持提现开关缺失时自动补齐创建');
assert(content.includes("'shop.recharge_withdraw.withdraw' && !isset($config['status'])"), 'getConfigs 支持提现配置缺省 status 时的智能兼容');
}
// ----------------------------------------------------
// 4. 服务层 Withdraw.php 与控制器验证
// ----------------------------------------------------
console.log('\n--- 4. 提现服务层 (Withdraw.php) 校验规则验证 ---');
const withdrawServicePath = path.resolve(__dirname, '../../addons/shopro/service/Withdraw.php');
assert(fs.existsSync(withdrawServicePath), 'Withdraw.php 服务文件存在');
if (fs.existsSync(withdrawServicePath)) {
const content = fs.readFileSync(withdrawServicePath, 'utf-8');
assert(content.includes("config['is_withdraw_open'] = (bool)($config['status'] === 1 && !empty($config['methods']))"), '构造函数综合计算 is_withdraw_open 标志');
assert(content.includes("intval($this->config['status']) === 0"), 'checkApply 校验提现总开关');
assert(content.includes("empty($this->config['methods'])"), 'checkApply 校验提现方式配置非空');
assert(content.includes('系统提现功能已关闭'), '提现关闭时抛出面向用户的明确提示文案');
}
const indexControllerPath = path.resolve(__dirname, '../../addons/shopro/controller/Index.php');
assert(fs.existsSync(indexControllerPath), 'Index.php 控制器存在');
if (fs.existsSync(indexControllerPath)) {
const content = fs.readFileSync(indexControllerPath, 'utf-8');
assert(content.includes("'withdraw_status' => (bool)"), 'Index.php init 接口下发 withdraw_status');
}
// ----------------------------------------------------
// 5. 用户端钱包资产页面 (money.vue) 对齐验证
// ----------------------------------------------------
console.log('\n--- 5. 用户端钱包资产页面 (money.vue) 验证 ---');
const moneyVuePath = path.resolve(__dirname, '../../uniapp/pages/user/wallet/money.vue');
assert(fs.existsSync(moneyVuePath), 'money.vue 文件存在');
if (fs.existsSync(moneyVuePath)) {
const content = fs.readFileSync(moneyVuePath, 'utf-8');
assert(content.includes('v-if="state.canWithdraw"'), '提现按钮使用 state.canWithdraw 控制显隐');
assert(content.includes('<text>提现</text>'), '按钮文案已由「充值」改为「提现」');
assert(!content.includes("@tap=\"sheep.$router.go('/pages/pay/recharge')\""), '已移除直接跳转充值页面的绑定');
assert(content.includes('@tap="onWithdraw"'), '点击事件绑定 onWithdraw');
assert(content.includes("canWithdraw: false"), 'state.canWithdraw 默认设为 false 防未授权闪现');
assert(content.includes("async function checkWithdrawStatus()"), '实现 checkWithdrawStatus 开关校验方法');
assert(content.includes("sheep.$api.pay.withdraw.rules()"), '调用提现规则接口获取实时开关状态');
assert(content.includes("sheep.$router.go('/pages/pay/withdraw')"), '校验通过后跳转标准提现页面 /pages/pay/withdraw');
assert(content.includes("checkWithdrawStatus()"), 'onLoad 与 onPullDownRefresh 触发检测');
}
// ----------------------------------------------------
// 6. 用户端申请提现页面 (withdraw.vue) 对齐验证
// ----------------------------------------------------
console.log('\n--- 6. 用户端申请提现页面 (withdraw.vue) 验证 ---');
const withdrawVuePath = path.resolve(__dirname, '../../uniapp/pages/pay/withdraw.vue');
assert(fs.existsSync(withdrawVuePath), 'withdraw.vue 文件存在');
if (fs.existsSync(withdrawVuePath)) {
const content = fs.readFileSync(withdrawVuePath, 'utf-8');
assert(content.includes('isWithdrawOpen'), '包含 isWithdrawOpen 响应式开关状态');
assert(content.includes("state.isWithdrawOpen ? '确认提现' : '提现暂未开启'"), '提现关闭时按钮提示「提现暂未开启」');
assert(content.includes("!state.isWithdrawOpen"), '提现关闭时按钮呈现灰度禁用态并在 onConfirm 中拦截');
}
// ----------------------------------------------------
// 7. 提现状态综合判定算法矩阵与异常拦截模拟
// ----------------------------------------------------
console.log('\n--- 7. 提现状态综合判定算法矩阵与异常拦截模拟 ---');
function evaluateWithdrawOpen(config) {
const status = config.status !== undefined
? Number(config.status)
: (Array.isArray(config.methods) && config.methods.length > 0 ? 1 : 0);
const hasMethods = Array.isArray(config.methods) && config.methods.length > 0;
return status === 1 && hasMethods;
}
function simulateCheckApply(config, method, amount, commission) {
if (config.status !== undefined && Number(config.status) === 0) {
throw new Error('系统提现功能已关闭');
}
if (!config.methods || config.methods.length === 0) {
throw new Error('暂未开启任何提现方式');
}
if (!config.methods.includes(method)) {
throw new Error('暂不支持该提现方式');
}
if (amount <= 0) {
throw new Error('请输入正确的提现金额');
}
if (commission < amount) {
throw new Error('可提现佣金不足');
}
return { ok: true, message: '提现申请校验通过' };
}
// 场景 1: 开启且有方式
const case1 = evaluateWithdrawOpen({ status: 1, methods: ['wechat', 'alipay'] });
assert(case1 === true, '场景1: status=1 且 methods 非空 => 允许提现 (显示按钮)');
// 场景 2: 关闭且有方式
const case2 = evaluateWithdrawOpen({ status: 0, methods: ['wechat', 'alipay'] });
assert(case2 === false, '场景2: status=0 且 methods 非空 => 禁止提现 (隐藏按钮)');
// 场景 3: 开启但无方式
const case3 = evaluateWithdrawOpen({ status: 1, methods: [] });
assert(case3 === false, '场景3: status=1 但 methods 为空 => 禁止提现 (隐藏按钮)');
// 场景 4: 未配置 status 但有方式 (历史升级平滑兼容)
const case4 = evaluateWithdrawOpen({ methods: ['bank'] });
assert(case4 === true, '场景4: 未配置 status 但 methods 非空 => 自动兼容开启 (显示按钮)');
// 场景 5: 未配置 status 且 methods 为空
const case5 = evaluateWithdrawOpen({ methods: [] });
assert(case5 === false, '场景5: 未配置 status 且 methods 为空 => 判定关闭 (隐藏按钮)');
// 场景 6: 接口级别直接阻断测试
let errorMsg = '';
try {
simulateCheckApply({ status: 0, methods: ['wechat'] }, 'wechat', 100, 500);
} catch (e) {
errorMsg = e.message;
}
assert(errorMsg === '系统提现功能已关闭', '直接调用接口: status=0 时被正确拦截抛出「系统提现功能已关闭」');
errorMsg = '';
try {
simulateCheckApply({ status: 1, methods: [] }, 'wechat', 100, 500);
} catch (e) {
errorMsg = e.message;
}
assert(errorMsg === '暂未开启任何提现方式', '直接调用接口: methods 为空时被正确拦截抛出「暂未开启任何提现方式」');
const successResult = simulateCheckApply({ status: 1, methods: ['wechat'] }, 'wechat', 100, 500);
assert(successResult.ok === true, '直接调用接口: 条件完全满足时正常放行提现');
console.log('\n========================================================');
console.log(`测试完成!通过: ${passCount}, 失败: ${failCount}`);
console.log('========================================================');
if (failCount > 0) {
process.exit(1);
}