文件
Henglianshutong/tests/verify_commission_goods_isolation_spec.js

147 行
7.1 KiB
JavaScript

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

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

/**
* 计奖商品收益隔离与混合下单防套利专项验证测试
* 覆盖范围:PartnerRewardService, VoucherService, RateService, DailySettleService, ActiveService
*/
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. PartnerRewardService.php 验证
const partnerRewardServicePath = path.resolve(__dirname, '../addons/shopro/service/partner/PartnerRewardService.php');
assert(fs.existsSync(partnerRewardServicePath), 'PartnerRewardService.php 文件存在');
if (fs.existsSync(partnerRewardServicePath)) {
const content = fs.readFileSync(partnerRewardServicePath, 'utf-8');
assert(content.includes('public function getQualifiedPayFee'), '包含 getQualifiedPayFee() 方法');
assert(content.includes('CommissionGoods::GOODS_COMMISSION_STATUS_ON'), 'getQualifiedPayFee 校验商品计奖启用状态');
assert(content.includes("isset($ext['is_commission']) && !$ext['is_commission']"), 'getQualifiedPayFee 排除活动不可计奖商品项');
assert(content.includes('$qualifiedPayFee = $this->getQualifiedPayFee($order)'), 'isQualified() 复用 getQualifiedPayFee()');
assert(content.includes("PartnerConfig::get('partner_order_min_amount'"), 'isQualified() 支持配置订单最低计奖金额门槛');
assert(content.includes('$isCommissionItem'), 'refundPartial() 判定子商品是否为计奖商品');
assert(content.includes('if ($itemPayFee > 0 && $isCommissionItem)'), 'refundPartial() 仅对计奖商品缩减兑换券');
}
// 2. VoucherService.php 验证
const voucherServicePath = path.resolve(__dirname, '../addons/shopro/service/partner/VoucherService.php');
assert(fs.existsSync(voucherServicePath), 'VoucherService.php 文件存在');
if (fs.existsSync(voucherServicePath)) {
const content = fs.readFileSync(voucherServicePath, 'utf-8');
assert(content.includes('$rewardService->getQualifiedPayFee($order)'), 'grant() 使用 getQualifiedPayFee 获取计奖实付金额');
assert(content.includes('$total = round((float) $qualifiedPayFee * $times, 2)'), '兑换券总额基于计奖实付金额而非整单实付');
assert(content.includes("计奖实付{$qualifiedPayFee}"), '兑换券日志 formula 明确标记计奖实付基数');
assert(content.includes("'qualified_pay_fee' => $qualifiedPayFee"), '兑换券日志 ext 记录 qualified_pay_fee');
}
// 3. RateService.php 验证
const rateServicePath = path.resolve(__dirname, '../addons/shopro/service/partner/RateService.php');
assert(fs.existsSync(rateServicePath), 'RateService.php 文件存在');
if (fs.existsSync(rateServicePath)) {
const content = fs.readFileSync(rateServicePath, 'utf-8');
assert(content.includes('public function qualifiedGmv'), 'RateService 包含 qualifiedGmv() 方法');
assert(content.includes('CommissionGoods::GOODS_COMMISSION_STATUS_ON'), 'qualifiedGmv 关联计奖状态表');
assert(content.includes('"is_commission":false'), 'qualifiedGmv SQL 排除非分销活动项');
assert(content.includes("$query->sum('oi.pay_fee')"), 'qualifiedGmv 针对 oi.pay_fee 单品金额求和');
}
// 4. DailySettleService.php 验证
const dailySettleServicePath = path.resolve(__dirname, '../addons/shopro/service/partner/DailySettleService.php');
assert(fs.existsSync(dailySettleServicePath), 'DailySettleService.php 文件存在');
if (fs.existsSync(dailySettleServicePath)) {
const content = fs.readFileSync(dailySettleServicePath, 'utf-8');
assert(content.includes('$gmv = $rateService->qualifiedGmv($start, $end)'), 'DailySettleService 日结 GMV 改用 qualifiedGmv 计算');
assert(!content.includes("bcadd((string) $gmv, (string) $order['pay_fee']"), 'DailySettleService 彻底移除按整单累加 GMV 的漏洞逻辑');
}
// 5. ActiveService.php 验证
const activeServicePath = path.resolve(__dirname, '../addons/shopro/service/partner/ActiveService.php');
assert(fs.existsSync(activeServicePath), 'ActiveService.php 文件存在');
if (fs.existsSync(activeServicePath)) {
const content = fs.readFileSync(activeServicePath, 'utf-8');
assert(content.includes('$rateService->qualifiedOrderQuery($start, $end)'), 'ActiveService 兜底保活查询复用 qualifiedOrderQuery');
}
// 6. 算法逻辑模拟运行断言
console.log('\n--- 场景模拟算法断言 ---');
function mockGetQualifiedPayFee(order, commissionGoodsMap) {
let total = 0;
const items = order.items || [];
for (const item of items) {
if (item.ext && item.ext.is_commission === false) {
continue;
}
if (commissionGoodsMap[item.goods_id] && commissionGoodsMap[item.goods_id].status === 1) {
total += Number(item.pay_fee || 0);
}
}
return Math.round(total * 100) / 100;
}
const mockCommissionGoods = {
101: { status: 1 }, // 指定计奖酒水
102: { status: 0 }, // 未开启计奖
// 103 不在映射表中,相当于新添加未配置的商品
};
// 场景 A:纯非计奖新品下单(商品 103,实付 500 元)
const pureNewOrder = {
id: 1,
pay_fee: 500,
items: [
{ goods_id: 103, pay_fee: 500, ext: {} }
]
};
const pureFee = mockGetQualifiedPayFee(pureNewOrder, mockCommissionGoods);
assert(pureFee === 0, '模拟场景 A:纯新品订单计奖实付金额为 0');
assert(pureFee > 0 === false, '模拟场景 A:纯新品订单无法通过 isQualified 校验');
// 场景 B:混合下单(商品 101 计奖 100 元,商品 103 新品 900 元,整单实付 1000 元)
const mixedOrder = {
id: 2,
pay_fee: 1000,
items: [
{ goods_id: 101, pay_fee: 100, ext: {} },
{ goods_id: 103, pay_fee: 900, ext: {} }
]
};
const mixedFee = mockGetQualifiedPayFee(mixedOrder, mockCommissionGoods);
assert(mixedFee === 100, '模拟场景 B:混合订单精确提取计奖实付 100 元(排除新品 900 元)');
const voucherTimes = 1.5;
const voucherTotal = Math.round(mixedFee * voucherTimes * 100) / 100;
assert(voucherTotal === 150, '模拟场景 B:兑换券按 100 * 1.5 = 150 发放(修复前会发 1500,成功堵住漏洞)');
// 场景 C:活动标记不参与分销(商品 101 秒杀特惠 is_commission=false)
const promoOrder = {
id: 3,
pay_fee: 100,
items: [
{ goods_id: 101, pay_fee: 100, ext: { is_commission: false } }
]
};
const promoFee = mockGetQualifiedPayFee(promoOrder, mockCommissionGoods);
assert(promoFee === 0, '模拟场景 C:即便属于计奖商品,活动标记 is_commission=false 亦被正确排除');
console.log('\n--------------------------------------------------------');
console.log(`测试执行结果: 通过 ${passCount} 项, 失败 ${failCount} 项`);
console.log('========================================================');
if (failCount > 0) {
process.exit(1);
}