文件
Henglianshutong/tests/verify_admin_withdraw_export_spec.js

249 行
12 KiB
JavaScript

此文件含有模棱两可的 Unicode 字符

此文件含有可能会与其他字符混淆的 Unicode 字符。 如果您是想特意这样的,可以安全地忽略该警告。 使用 Escape 按钮显示他们。

/**
* 后台提现管理(shopro/withdraw)导出功能全链路规格与算法模拟测试
* 覆盖范围:
* 1. application/admin/controller/shopro/Withdraw.php 控制器 export 方法、权限配置、save_type 配置注入
* 2. application/admin/model/shopro/Withdraw.php 模型 user 关联包含 mobile 字段
* 3. application/admin/view/shopro/withdraw/index.html 视图导出按钮与权限指令
* 4. public/assets/js/backend/shopro/withdraw.js 前端脚本 onExport 方法与 Vue ref/exportLoading 响应式变量
* 5. 提现数据导出映射逻辑(普通余额提现 vs DMEPA数字资产提现)单元与格式算法模拟
* 6. 大数防科学计数法(\t 前缀)、汇总行金额精确累加逻辑模拟断言
*/
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(' 后台提现管理订单导出功能全链路专项测试');
console.log('========================================================\n');
// ----------------------------------------------------
// 1. Withdraw.php 控制器层导出功能验证
// ----------------------------------------------------
console.log('--- 1. application/admin/controller/shopro/Withdraw.php 控制器导出逻辑 ---');
const controllerPath = path.resolve(__dirname, '../application/admin/controller/shopro/Withdraw.php');
testAssert(fs.existsSync(controllerPath), 'Withdraw.php 控制器文件存在');
if (fs.existsSync(controllerPath)) {
const content = fs.readFileSync(controllerPath, 'utf-8');
testAssert(content.includes("'export'"), '$noNeedRight 包含 export 白名单');
testAssert(content.includes('$this->assignconfig("save_type"'), 'index() 注入 save_type 配置');
testAssert(content.includes('public function export()'), '控制器包含 public function export() 方法');
testAssert(content.includes("$this->model->sheepFilter()"), 'export 使用 $this->model->sheepFilter() 进行数据过滤');
testAssert(content.includes("addons\\shopro\\library\\Export"), '引入 Shopro 统一导出库 Export');
testAssert(content.includes("'商户单号'"), '导出表头包含 商户单号');
testAssert(content.includes("'申请用户'"), '导出表头包含 申请用户');
testAssert(content.includes("'用户手机号'"), '导出表头包含 用户手机号');
testAssert(content.includes("'提现资产'"), '导出表头包含 提现资产');
testAssert(content.includes("提现金额/换算市值"), '导出表头包含 提现金额/换算市值');
testAssert(content.includes("实际到账"), '导出表头包含 实际到账');
testAssert(content.includes("'提现方式'"), '导出表头包含 提现方式');
testAssert(content.includes("银行卡号"), '导出表头包含 银行卡号');
testAssert(content.includes("'资产折算明细'"), '导出表头包含 资产折算明细');
testAssert(content.includes('"\\t"'), '导出对长订单号、卡号等使用制表符防止科学计数法');
testAssert(content.includes("is_madow_withdraw"), '正确区分 DMEPA 资产提现与普通余额提现');
testAssert(content.includes("'id' => '合计: 共 ' . $total"), '生成底部汇总统计行');
}
// ----------------------------------------------------
// 2. Withdraw.php 模型层用户信息关联验证
// ----------------------------------------------------
console.log('\n--- 2. application/admin/model/shopro/Withdraw.php 模型层验证 ---');
const modelPath = path.resolve(__dirname, '../application/admin/model/shopro/Withdraw.php');
testAssert(fs.existsSync(modelPath), 'Withdraw.php 模型文件存在');
if (fs.existsSync(modelPath)) {
const content = fs.readFileSync(modelPath, 'utf-8');
testAssert(content.includes("field('id, nickname, mobile, avatar, total_consume')"), 'user 关联字段包含 mobile');
}
// ----------------------------------------------------
// 3. index.html 视图层导出按钮与权限验证
// ----------------------------------------------------
console.log('\n--- 3. application/admin/view/shopro/withdraw/index.html 视图层验证 ---');
const viewPath = path.resolve(__dirname, '../application/admin/view/shopro/withdraw/index.html');
testAssert(fs.existsSync(viewPath), 'index.html 视图文件存在');
if (fs.existsSync(viewPath)) {
const content = fs.readFileSync(viewPath, 'utf-8');
testAssert(content.includes("onExport('export')"), '页面包含 onExport 导出点击事件');
testAssert(content.includes(":loading=\"exportLoading\""), '按钮绑定 exportLoading 加载状态');
testAssert(content.includes("$auth->check('shopro/withdraw/export')"), '导出按钮包裹权限校验');
}
// ----------------------------------------------------
// 4. withdraw.js 前端脚本层验证
// ----------------------------------------------------
console.log('\n--- 4. public/assets/js/backend/shopro/withdraw.js 前端脚本验证 ---');
const jsPath = path.resolve(__dirname, '../public/assets/js/backend/shopro/withdraw.js');
testAssert(fs.existsSync(jsPath), 'withdraw.js 前端脚本文件存在');
if (fs.existsSync(jsPath)) {
const content = fs.readFileSync(jsPath, 'utf-8');
testAssert(content.includes("const { reactive, onMounted, ref } = Vue"), '从 Vue 中解构引入 ref');
testAssert(content.includes("const exportLoading = ref(false)"), '定义 exportLoading 响应式变量');
testAssert(content.includes("function onExport(type = 'export')"), '实现 onExport 导出处理方法');
testAssert(content.includes("Config.save_type == 'download'"), '支持直接浏览器下载模式 (Config.save_type == download)');
testAssert(content.includes("Fast.api.ajax"), '支持异步导出模式 (Fast.api.ajax)');
testAssert(content.includes("exportLoading,") && content.includes("onExport,"), 'setup() return 暴露 exportLoading 与 onExport');
}
// ----------------------------------------------------
// 5. 提现数据导出格式化算法与业务模拟
// ----------------------------------------------------
console.log('\n--- 5. 提现数据导出格式化算法与业务模拟 ---');
// 模拟导出映射函数(与控制器逻辑对齐)
function formatExportRow(row) {
const isMadow = Boolean(row.is_madow_withdraw);
const withdrawInfo = row.withdraw_info || {};
const withdrawType = row.withdraw_type || '';
const withdrawTypeMap = {
'wechat': '微信零钱',
'alipay': '支付宝',
'bank': '银行卡',
};
const statusMap = {
'0': '待审核',
'1': '处理中',
'2': '已处理',
'-1': '已拒绝',
};
let realName = '';
let bankName = '';
let accountNo = '';
if (withdrawType === 'bank') {
realName = withdrawInfo.real_name || '';
bankName = withdrawInfo.bank_name || '';
accountNo = withdrawInfo.card_no ? `\t${withdrawInfo.card_no}` : '';
} else if (withdrawType === 'alipay') {
realName = withdrawInfo.real_name || '';
accountNo = withdrawInfo.account ? `\t${withdrawInfo.account}` : '';
} else if (withdrawType === 'wechat') {
realName = withdrawInfo.real_name || '';
accountNo = withdrawInfo.openid ? `\t${withdrawInfo.openid}` : '';
}
let assetName = '钱包余额';
let assetAmountDetail = '-';
let amountDisplay = Number(row.money || 0).toFixed(2);
let actualAmountDisplay = Number(row.actual_money || 0).toFixed(2);
let chargeMoneyDisplay = Number(row.charge_money || 0).toFixed(2);
let serviceFeeRateDisplay = `${row.service_fee || 0}%`;
if (isMadow) {
assetName = 'DMEPA数字资产';
const madowAmount = Number(withdrawInfo.madow_amount || 0);
const madowRate = Number(withdrawInfo.madow_rate || 0);
assetAmountDetail = `DMEPA: ${madowAmount.toFixed(4)}, 汇率: ¥${madowRate.toFixed(4)}`;
amountDisplay = `¥${Number(row.money || 0).toFixed(2)}`;
actualAmountDisplay = `¥${Number(row.actual_money || 0).toFixed(2)}`;
}
return {
id: row.id,
sn: `\t${row.sn}`,
user: `[${row.user_id}] ${row.user ? row.user.nickname : ''}`,
mobile: row.user && row.user.mobile ? `\t${row.user.mobile}` : '-',
asset_name: assetName,
money: amountDisplay,
actual_money: actualAmountDisplay,
service_fee: serviceFeeRateDisplay,
charge_money: chargeMoneyDisplay,
withdraw_type_text: withdrawTypeMap[withdrawType] || withdrawType,
real_name: realName,
bank_name: bankName,
card_no: accountNo,
asset_detail: assetAmountDetail,
status_text: statusMap[String(row.status)] || String(row.status),
status_msg: row.status_msg || '-',
createtime: '2026-09-21 14:00:00',
updatetime: '2026-09-21 14:10:00',
};
}
// 模拟场景 A:普通钱包余额提现至银行卡
const mockRowA = {
id: 101,
sn: 'TX202609210001',
user_id: 8888,
user: { id: 8888, nickname: '创客小张', mobile: '13800138000' },
is_madow_withdraw: false,
money: 1000.00,
actual_money: 990.00,
charge_money: 10.00,
service_fee: 1.0,
withdraw_type: 'bank',
withdraw_info: {
real_name: '张三',
bank_name: '中国工商银行',
card_no: '6222021000112233445'
},
status: 2,
status_msg: '打款成功',
};
const formattedA = formatExportRow(mockRowA);
testAssert(formattedA.asset_name === '钱包余额', '普通余额提现资产显示为 钱包余额');
testAssert(formattedA.mobile === '\t13800138000', '手机号添加制表符防失真');
testAssert(formattedA.card_no === '\t6222021000112233445', '银行卡号添加制表符防科学计数法');
testAssert(formattedA.money === '1000.00', '提现金额精准显示 1000.00');
testAssert(formattedA.actual_money === '990.00', '实际到账金额精准显示 990.00');
testAssert(formattedA.charge_money === '10.00', '手续费精准显示 10.00');
testAssert(formattedA.status_text === '已处理', '状态正确转为 已处理');
// 模拟场景 B:DMEPA 数字资产提现
const mockRowB = {
id: 102,
sn: 'TX202609210002',
user_id: 9999,
user: { id: 9999, nickname: '合伙人李四', mobile: '13900139000' },
is_madow_withdraw: true,
money: 5000.00,
actual_money: 4950.00,
charge_money: 50.00,
service_fee: 1.0,
withdraw_type: 'bank',
withdraw_info: {
real_name: '李四',
bank_name: '招商银行',
card_no: '6214830123456789',
madow_amount: 1000.0000,
madow_rate: 5.0000,
},
status: 0,
status_msg: '',
};
const formattedB = formatExportRow(mockRowB);
testAssert(formattedB.asset_name === 'DMEPA数字资产', 'DMEPA提现资产显示为 DMEPA数字资产');
testAssert(formattedB.asset_detail.includes('DMEPA: 1000.0000'), '资产折算明细包含 DMEPA 数量');
testAssert(formattedB.asset_detail.includes('汇率: ¥5.0000'), '资产折算明细包含汇率');
testAssert(formattedB.status_text === '待审核', '状态正确转为 待审核');
// 模拟场景 C:合计行计算验证
const totalMoney = mockRowA.money + mockRowB.money;
const totalActualMoney = mockRowA.actual_money + mockRowB.actual_money;
const totalChargeMoney = mockRowA.charge_money + mockRowB.charge_money;
testAssert(totalMoney === 6000.00, '提现总额合计为 6000.00 元');
testAssert(totalActualMoney === 5940.00, '实际到账总额合计为 5940.00 元');
testAssert(totalChargeMoney === 60.00, '手续费总额合计为 60.00 元');
console.log('\n========================================================');
console.log(`测试完成!通过: ${passCount}, 失败: ${failCount}`);
console.log('========================================================\n');
if (failCount > 0) {
process.exit(1);
}