/** * tests/partner/verify_withdraw_methods_visibility_spec.js * * 验证提现方式选择弹窗(account-type-select.vue)与提现主页(withdraw.vue) * “如果后台设置关闭的 这里隐藏掉 只有开启才显示” 业务规范自动化测试: * * 1. 组件源码结构静态检查(filteredTypeList 仅渲染后台开启的提现渠道) * 2. 彻底废除旧的 disabled 灰色禁用行显示,改为未开启方式完全不渲染 * 3. 提现主页面关闭渠道自愈与单渠道智能预选逻辑验证 * 4. 各种后台配置场景模拟(单银行卡、微信+银行卡、全开、全关等) * 5. API 模拟测试(正常场景、业务拒绝场景、参数边界场景) */ const fs = require('fs'); const path = require('path'); const assert = require('assert'); console.log('========================================================'); console.log(' 验证:提现方式选择弹窗后台关闭项完全隐藏专项测试'); console.log('========================================================\n'); let passCount = 0; let failCount = 0; function runTest(title, fn) { try { fn(); console.log(`[PASS] ${title}`); passCount++; } catch (err) { console.error(`[FAIL] ${title}`); console.error(` 错误原因: ${err.message}`); failCount++; } } const rootDir = path.resolve(__dirname, '../../'); // 1. 验证 uniapp/pages/pay/components/account-type-select.vue runTest('account-type-select.vue 源码实现 filteredTypeList 动态过滤与彻底隐藏未开启项', () => { const file = path.join(rootDir, 'uniapp/pages/pay/components/account-type-select.vue'); assert.ok(fs.existsSync(file), 'account-type-select.vue 文件存在'); const content = fs.readFileSync(file, 'utf-8'); // 验证 template 中通过 filteredTypeList 遍历,而非全量 typeList assert.ok(content.includes('v-for="(item, index) in filteredTypeList"'), '必须遍历 filteredTypeList'); assert.ok(!content.includes(':disabled="!methods.includes(item.value)"'), '严禁以 disabled 形式展示未开启的灰色提现项'); // 验证 computed 定义与过滤逻辑 assert.ok(content.includes('const filteredTypeList = computed('), '必须包含 filteredTypeList 计算属性'); assert.ok(content.includes('props.methods.includes(item.value)'), '必须根据 props.methods 严格过滤项'); // 验证空状态提示 assert.ok(content.includes('暂无可用的提现方式'), '包含全关闭时的友好兜底提示'); // 验证响应式同步 assert.ok(content.includes('function syncCurrentValue('), '包含有效提现方式同步校验函数'); }); // 2. 验证 uniapp/pages/pay/withdraw.vue 协同自愈逻辑 runTest('withdraw.vue 规则加载后自动清理后台已关闭的提现方式并支持单方式预选', () => { const file = path.join(rootDir, 'uniapp/pages/pay/withdraw.vue'); assert.ok(fs.existsSync(file), 'withdraw.vue 文件存在'); const content = fs.readFileSync(file, 'utf-8'); // 验证对已失效提现方式的清理逻辑 assert.ok( content.includes('!data.methods.includes(state.accountInfo.type)'), '当后台关闭某提现方式时,清空当前失效选中' ); // 验证单提现方式自动预选 assert.ok( content.includes('data.methods.length === 1'), '当后台仅开启1种提现方式时,自动预选该方式' ); }); // 3. 核心算法模拟测试:矩阵过滤验证 const allTypes = [ { icon: 'wechat.png', title: '微信零钱', value: 'wechat' }, { icon: 'alipay.png', title: '支付宝账户', value: 'alipay' }, { icon: 'bank.png', title: '银行卡转账', value: 'bank' }, ]; function filterTypeList(methods) { if (!Array.isArray(methods) || methods.length === 0) return []; return allTypes.filter((item) => methods.includes(item.value)); } runTest('场景1(如用户截图):后台仅开启银行卡,微信与支付宝必须完全隐藏', () => { const result = filterTypeList(['bank']); assert.strictEqual(result.length, 1); assert.strictEqual(result[0].value, 'bank'); assert.strictEqual(result[0].title, '银行卡转账'); const hasWechat = result.some((r) => r.value === 'wechat'); const hasAlipay = result.some((r) => r.value === 'alipay'); assert.strictEqual(hasWechat, false, '微信零钱必须完全隐藏'); assert.strictEqual(hasAlipay, false, '支付宝账户必须完全隐藏'); }); runTest('场景2:后台开启微信和银行卡,支付宝关闭,仅显示微信和银行卡', () => { const result = filterTypeList(['wechat', 'bank']); assert.strictEqual(result.length, 2); assert.deepStrictEqual(result.map(r => r.value), ['wechat', 'bank']); const hasAlipay = result.some((r) => r.value === 'alipay'); assert.strictEqual(hasAlipay, false, '支付宝账户必须完全隐藏'); }); runTest('场景3:后台开启全部三种提现方式,三者均正常显示', () => { const result = filterTypeList(['wechat', 'alipay', 'bank']); assert.strictEqual(result.length, 3); assert.deepStrictEqual(result.map(r => r.value), ['wechat', 'alipay', 'bank']); }); runTest('场景4:后台关闭所有提现方式,列表为空且触发兜底', () => { const result1 = filterTypeList([]); assert.strictEqual(result1.length, 0); const result2 = filterTypeList(null); assert.strictEqual(result2.length, 0); }); // 4. API 模拟测试 runTest('API 模拟测试:pay/withdraw/rules 返回仅包含 bank 的提现规则', () => { const mockRulesResponse = { code: 1, msg: '提现规则', data: { status: 1, is_withdraw_open: true, min_amount: '1.00', max_amount: '50000.00', charge_rate_format: 0.5, charge_rate: 0.005, methods: ['bank'], auto_arrival: 0, } }; assert.strictEqual(mockRulesResponse.code, 1); assert.deepStrictEqual(mockRulesResponse.data.methods, ['bank']); const visible = filterTypeList(mockRulesResponse.data.methods); assert.strictEqual(visible.length, 1); assert.strictEqual(visible[0].value, 'bank'); }); runTest('API 模拟测试:提现申请发起时若渠道已被关闭,服务端拦截', () => { const mockConfig = { status: 1, methods: ['bank'] }; function simulateApply(type) { if (!mockConfig.methods.includes(type)) { return { code: 3001, message: '暂不支持该提现方式' }; } return { code: 0, message: '操作成功', data: { withdraw_sn: 'W123456789' } }; } const resWechat = simulateApply('wechat'); assert.strictEqual(resWechat.code, 3001); assert.strictEqual(resWechat.message, '暂不支持该提现方式'); const resBank = simulateApply('bank'); assert.strictEqual(resBank.code, 0); assert.strictEqual(resBank.data.withdraw_sn, 'W123456789'); }); runTest('API 模拟测试:参数异常(非法提现方式字符串)前端与接口防穿透拦截', () => { const allowed = ['bank']; const testInput = 'malicious_eval_type'; const isAllowed = allowed.includes(testInput); assert.strictEqual(isAllowed, false); }); console.log('\n========================================================'); console.log(`测试完成: 成功 ${passCount} 项, 失败 ${failCount} 项`); console.log('========================================================'); if (failCount > 0) { process.exit(1); }