feat(dmepa): 全局统一规范DMEPA命名并优化游客与品酒师身份显示
这个提交包含在:
@@ -104,7 +104,7 @@ class DashboardWealthTest
|
||||
'total() 返回完整的 wealth_data 资金盘宏观指标');
|
||||
|
||||
$this->assert(!empty($madow) && isset($madow['total_madow'], $madow['current_rate'], $madow['rate_per_wan'], $madow['total_madow_cny']),
|
||||
'total() 返回完整的 madow_data 马豆资产与折算人民币指标');
|
||||
'total() 返回完整的 madow_data dmepa资产与折算人民币指标');
|
||||
|
||||
$this->assert(!empty($voucher) && isset($voucher['total_voucher'], $voucher['released_voucher'], $voucher['pending_voucher']),
|
||||
'total() 返回完整的 voucher_data 消费兑换券杠杆资金池指标');
|
||||
@@ -120,7 +120,7 @@ class DashboardWealthTest
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试 2:每一个角色身份的会员数、马豆数量、分佣数量与马豆价值精算校验
|
||||
* 测试 2:每一个角色身份的会员数、dmepa数量、分佣数量与dmepa价值精算校验
|
||||
*/
|
||||
public function testAllRolesStatisticsIntegrity()
|
||||
{
|
||||
@@ -134,7 +134,7 @@ class DashboardWealthTest
|
||||
$this->assert(isset($roleStats[$roleKey]), "矩阵包含角色身份: {$roleKey}");
|
||||
$item = $roleStats[$roleKey] ?? [];
|
||||
$this->assert(isset($item['user_count'], $item['madow_total'], $item['madow_cny'], $item['reward_amount'], $item['reward_count'], $item['active_rate']),
|
||||
"角色 {$roleKey} 完整包含会员数、持豆量、马豆市值、分佣总额、分佣笔数与活跃率");
|
||||
"角色 {$roleKey} 完整包含会员数、持豆量、dmepa市值、分佣总额、分佣笔数与活跃率");
|
||||
}
|
||||
|
||||
// 普通会员无需自购考核标识
|
||||
|
||||
@@ -67,7 +67,7 @@ class MultiAssetRechargeTest
|
||||
public function run()
|
||||
{
|
||||
echo "========================================================\n";
|
||||
echo " 会员多资产(余额/积分/马豆/兑换券)充值业务测试 \n";
|
||||
echo " 会员多资产(余额/积分/dmepa/兑换券)充值业务测试 \n";
|
||||
echo "========================================================\n\n";
|
||||
|
||||
$this->setUp();
|
||||
@@ -291,41 +291,41 @@ class MultiAssetRechargeTest
|
||||
|
||||
public function testMadowRechargeAndDeduct()
|
||||
{
|
||||
// 充值 5.12345678 颗马豆
|
||||
// 充值 5.12345678 颗dmepa
|
||||
$res1 = $this->invokeRecharge([
|
||||
'id' => $this->testUserId,
|
||||
'type' => 'madow',
|
||||
'amount' => '5.12345678',
|
||||
'memo' => '充值马豆',
|
||||
'memo' => '充值dmepa',
|
||||
]);
|
||||
$agent1 = AgentModel::where('user_id', $this->testUserId)->find();
|
||||
$this->assert($res1['code'] === 1 && $agent1->madow == '15.12345678', '马豆8位高精度充值到账', "当前马豆: {$agent1->madow}");
|
||||
$this->assert($res1['code'] === 1 && $agent1->madow == '15.12345678', 'dmepa8位高精度充值到账', "当前dmepa: {$agent1->madow}");
|
||||
|
||||
// 扣除 3.00000000 颗马豆
|
||||
// 扣除 3.00000000 颗dmepa
|
||||
$res2 = $this->invokeRecharge([
|
||||
'id' => $this->testUserId,
|
||||
'type' => 'madow',
|
||||
'amount' => '-3.00000000',
|
||||
'memo' => '扣除马豆',
|
||||
'memo' => '扣除dmepa',
|
||||
]);
|
||||
$agent2 = AgentModel::where('user_id', $this->testUserId)->find();
|
||||
$this->assert($res2['code'] === 1 && $agent2->madow == '12.12345678', '马豆高精度扣除成功', "当前马豆: {$agent2->madow}");
|
||||
$this->assert($res2['code'] === 1 && $agent2->madow == '12.12345678', 'dmepa高精度扣除成功', "当前dmepa: {$agent2->madow}");
|
||||
|
||||
// 验证流水表记录
|
||||
$log = RewardModel::where('user_id', $this->testUserId)->where('reward_type', 'admin_madow')->order('id', 'desc')->find();
|
||||
$this->assert($log && $log->status == 1, '马豆充值写入收益流水明细');
|
||||
$this->assert($log && $log->status == 1, 'dmepa充值写入收益流水明细');
|
||||
}
|
||||
|
||||
public function testMadowOverdrawn()
|
||||
{
|
||||
// 尝试超额扣除马豆
|
||||
// 尝试超额扣除dmepa
|
||||
$res = $this->invokeRecharge([
|
||||
'id' => $this->testUserId,
|
||||
'type' => 'madow',
|
||||
'amount' => '-99.00000000',
|
||||
'memo' => '超额扣除马豆',
|
||||
'memo' => '超额扣除dmepa',
|
||||
]);
|
||||
$this->assert($res['code'] === 0 && strpos($res['msg'], '不足') !== false, '马豆持仓不足拦截超扣', $res['msg'] ?? '');
|
||||
$this->assert($res['code'] === 0 && strpos($res['msg'], '不足') !== false, 'dmepa持仓不足拦截超扣', $res['msg'] ?? '');
|
||||
}
|
||||
|
||||
public function testVoucherRechargeAndDeduct()
|
||||
|
||||
@@ -71,15 +71,15 @@ function doRecharge($type, $amount, $memo)
|
||||
|
||||
echo "=== 开始对会员 10 (尚善) 真实链路充值验证 ===\n";
|
||||
|
||||
// 1. 马豆充值 +8 颗
|
||||
$r1 = doRecharge('madow', '8.00000000', '真实环境测试充值马豆');
|
||||
// 1. dmepa充值 +8 颗
|
||||
$r1 = doRecharge('madow', '8.00000000', '真实环境测试充值dmepa');
|
||||
$agent1 = Db::name('shopro_commission_agent')->where('user_id', 10)->find();
|
||||
echo "1. 充值马豆+8结果: " . json_encode($r1, JSON_UNESCAPED_UNICODE) . " 当前持仓: " . ($agent1['madow'] ?? '0') . "\n";
|
||||
echo "1. 充值dmepa+8结果: " . json_encode($r1, JSON_UNESCAPED_UNICODE) . " 当前持仓: " . ($agent1['madow'] ?? '0') . "\n";
|
||||
|
||||
// 2. 马豆扣除 -8 颗
|
||||
$r2 = doRecharge('madow', '-8.00000000', '真实环境测试扣除马豆');
|
||||
// 2. dmepa扣除 -8 颗
|
||||
$r2 = doRecharge('madow', '-8.00000000', '真实环境测试扣除dmepa');
|
||||
$agent2 = Db::name('shopro_commission_agent')->where('user_id', 10)->find();
|
||||
echo "2. 扣除马豆-8结果: " . json_encode($r2, JSON_UNESCAPED_UNICODE) . " 恢复持仓: " . ($agent2['madow'] ?? '0') . "\n";
|
||||
echo "2. 扣除dmepa-8结果: " . json_encode($r2, JSON_UNESCAPED_UNICODE) . " 恢复持仓: " . ($agent2['madow'] ?? '0') . "\n";
|
||||
|
||||
// 3. 兑换券充值 +20 元
|
||||
$r3 = doRecharge('voucher', '20.00', '真实环境测试充值兑换券');
|
||||
@@ -105,7 +105,7 @@ echo "6. 扣除积分-10结果: " . json_encode($r6, JSON_UNESCAPED_UNICODE) . "
|
||||
$rewards = Db::name('shopro_partner_reward')->where('user_id', 10)->whereIn('reward_type', ['admin_madow', 'admin_voucher'])->order('id', 'desc')->limit(4)->select();
|
||||
echo "\n=== 会员 10 收益流水落库检查 (共 " . count($rewards) . " 笔充值/扣除流水) ===\n";
|
||||
foreach ($rewards as $idx => $item) {
|
||||
echo " [" . ($idx + 1) . "] 类型: {$item['reward_type']} | 金额: {$item['amount']} | 马豆: {$item['madow']} | 备注: " . $item['ext'] . "\n";
|
||||
echo " [" . ($idx + 1) . "] 类型: {$item['reward_type']} | 金额: {$item['amount']} | dmepa: {$item['madow']} | 备注: " . $item['ext'] . "\n";
|
||||
}
|
||||
|
||||
echo "\n=== 会员 10 真实测试全流程 100% 成功完毕 ===\n";
|
||||
|
||||
@@ -32,7 +32,7 @@ assert(rewardModelContent.includes('function getWalletTypeTextAttr'), '包含 ge
|
||||
assert(rewardModelContent.includes("PartnerConfig::rewardWalletType()"), '现金分润缺省联动 PartnerConfig::rewardWalletType()');
|
||||
assert(rewardModelContent.includes("'商城余额'"), 'wallet_type_text 映射包含 商城余额');
|
||||
assert(rewardModelContent.includes("'佣金钱包'"), 'wallet_type_text 映射包含 佣金钱包');
|
||||
assert(rewardModelContent.includes("'分红马豆'"), 'wallet_type_text 映射包含 分红马豆');
|
||||
assert(rewardModelContent.includes("'分红dmepa'"), 'wallet_type_text 映射包含 分红dmepa');
|
||||
|
||||
// --- 2. Order.php 订单分成构建与入账账户对齐验证 ---
|
||||
console.log('\n--- 2. Order.php 订单分成构建与入账账户对齐验证 ---');
|
||||
|
||||
@@ -0,0 +1,94 @@
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const assert = require('assert');
|
||||
|
||||
console.log('========================================================');
|
||||
console.log(' 现金收益明细隔离虚拟资产与清除0元记录 专项测试');
|
||||
console.log('========================================================\n');
|
||||
|
||||
const rootDir = path.resolve(__dirname, '../../');
|
||||
|
||||
// 1. 验证 partner/Index.php
|
||||
const indexCtrlPath = path.join(rootDir, 'addons/shopro/controller/partner/Index.php');
|
||||
assert(fs.existsSync(indexCtrlPath), 'addons/shopro/controller/partner/Index.php 文件存在');
|
||||
const indexContent = fs.readFileSync(indexCtrlPath, 'utf8');
|
||||
|
||||
// 验证现金白名单与排他性
|
||||
assert(indexContent.includes('$cashTypes = array_merge(PartnerConfig::instantTypes(), [\'venue_share\', \'county_share\'])'), 'rewards 接口定义了专属现金类型白名单');
|
||||
assert(!indexContent.includes("'share' => ['venue_share', 'county_share', 'madow_share']"), '加权奖 Tab 中已彻底移除 madow_share (马豆分红)');
|
||||
assert(indexContent.includes("'share' => ['venue_share', 'county_share']"), '加权奖 Tab 仅保留纯现金均分奖励');
|
||||
assert(indexContent.includes("$query->whereIn('reward_type', $cashTypes)"), '全部 Tab 默认仅查询纯现金奖励白名单');
|
||||
|
||||
// 验证 0 元记录过滤
|
||||
assert(indexContent.includes("$q->where('amount', '>', 0)"), 'rewards 强制要求已入账记录金额大于0');
|
||||
assert(indexContent.includes("->whereOr('status', -1)"), '保留未达成的拦截记录');
|
||||
|
||||
// 验证来源文本规范化
|
||||
assert(indexContent.includes("'source_text' => $sourceText"), 'rewards 接口返回结构化 source_text');
|
||||
assert(indexContent.includes('全网加权日结分红'), '日结均分奖励明确来源文本为全网加权日结分红');
|
||||
console.log(' [✓ PASS] 服务端 partner/Index.php 现金类型白名单与0元过滤已就绪');
|
||||
|
||||
// 2. 验证前端 rewards.vue
|
||||
const rewardsVuePath = path.join(rootDir, 'uniapp/pages/commission/rewards.vue');
|
||||
assert(fs.existsSync(rewardsVuePath), 'rewards.vue 页面文件存在');
|
||||
const rewardsContent = fs.readFileSync(rewardsVuePath, 'utf8');
|
||||
|
||||
assert(rewardsContent.includes('item.source_text'), 'rewards.vue 优先展示 source_text');
|
||||
assert(!rewardsContent.includes("来自 {{ item.buyer_nickname || '好友' }}"), 'rewards.vue 彻底移除生硬的“来自 好友”兜底');
|
||||
assert(rewardsContent.includes('state.current.source_text'), 'rewards.vue 详情弹窗优先展示 source_text');
|
||||
console.log(' [✓ PASS] 前端 rewards.vue 来源展示优化与兜底修复已就绪');
|
||||
|
||||
// 3. 模拟逻辑单元测试
|
||||
const instantTypes = ['push3', 'maker_diff', 'maker_peer', 'maker_overtaken', 'team_venue', 'team_county', 'venue_peer', 'county_peer'];
|
||||
const cashTypes = [...instantTypes, 'venue_share', 'county_share'];
|
||||
|
||||
const mockRows = [
|
||||
{ id: 1, reward_type: 'push3', amount: '398.00', madow: '0', status: 1, buyer: { nickname: '张三' } },
|
||||
{ id: 2, reward_type: 'madow_share', amount: '0.00', madow: '120.00', status: 1, buyer: null }, // 每日贡献马豆
|
||||
{ id: 3, reward_type: 'venue_share', amount: '50.00', madow: '0', status: 1, buyer: null }, // 酒道馆均分现金
|
||||
{ id: 4, reward_type: 'voucher_release', amount: '0.00', madow: '0', status: 1, buyer: null }, // 券释放
|
||||
{ id: 5, reward_type: 'push3', amount: '0.00', madow: '0', status: -1, skip_reason: 'inactive_30d', buyer: { nickname: '李四' } }, // 未自购拦截
|
||||
{ id: 6, reward_type: 'push3', amount: '0.00', madow: '0', status: 1, buyer: null }, // 异常 0 元已入账记录
|
||||
];
|
||||
|
||||
// 模拟查询逻辑
|
||||
function filterRewards(tab) {
|
||||
return mockRows.filter(row => {
|
||||
// 1. 类型过滤
|
||||
if (tab === 'all') {
|
||||
if (!cashTypes.includes(row.reward_type)) return false;
|
||||
} else if (tab === 'share') {
|
||||
if (!['venue_share', 'county_share'].includes(row.reward_type)) return false;
|
||||
} else if (tab === 'push3') {
|
||||
if (row.reward_type !== 'push3') return false;
|
||||
}
|
||||
|
||||
// 2. 金额过滤: amount > 0 或者 status === -1
|
||||
const amountNum = parseFloat(row.amount);
|
||||
if (!(amountNum > 0 || row.status === -1)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
// 验证 全部 Tab
|
||||
const allList = filterRewards('all');
|
||||
assert(allList.some(r => r.id === 1), '全部 Tab 应包含正常推三返一');
|
||||
assert(allList.some(r => r.id === 3), '全部 Tab 应包含酒道馆日结现金均分');
|
||||
assert(allList.some(r => r.id === 5), '全部 Tab 应包含被拦截的跳过记录');
|
||||
assert(!allList.some(r => r.id === 2), '全部 Tab 绝不能包含每日贡献马豆 (madow_share)');
|
||||
assert(!allList.some(r => r.id === 4), '全部 Tab 绝不能包含券释放 (voucher_release)');
|
||||
assert(!allList.some(r => r.id === 6), '全部 Tab 绝不能包含已入账但金额为0的废弃记录');
|
||||
console.log(' [✓ PASS] 全部 Tab 纯现金过滤验证成功 (过滤掉所有 madow_share 与 0 元记录)');
|
||||
|
||||
// 验证 加权奖 Tab
|
||||
const shareList = filterRewards('share');
|
||||
assert(shareList.some(r => r.id === 3), '加权奖 Tab 包含酒道馆日结现金均分');
|
||||
assert(!shareList.some(r => r.id === 2), '加权奖 Tab 彻底排除马豆分红');
|
||||
console.log(' [✓ PASS] 加权奖 Tab 纯现金过滤验证成功');
|
||||
|
||||
console.log('\n========================================================');
|
||||
console.log(' 全部修复项与业务隔离测试 100% 通过!');
|
||||
console.log('========================================================');
|
||||
@@ -0,0 +1,156 @@
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const assert = require('assert');
|
||||
|
||||
console.log('========================================================');
|
||||
console.log(' 未买单用户显示“游客”、购买1单后显示“品酒师”专项测试');
|
||||
console.log('========================================================\n');
|
||||
|
||||
const rootDir = path.resolve(__dirname, '../../');
|
||||
|
||||
// 1. 验证 IdentityService.php
|
||||
const identityServicePath = path.join(rootDir, 'addons/shopro/service/partner/IdentityService.php');
|
||||
assert(fs.existsSync(identityServicePath), 'IdentityService.php 文件存在');
|
||||
const idServiceContent = fs.readFileSync(identityServicePath, 'utf8');
|
||||
|
||||
assert(idServiceContent.includes('function hasPurchased('), 'IdentityService 包含 hasPurchased 方法');
|
||||
assert(idServiceContent.includes("'total_consume'"), 'hasPurchased 优先使用 total_consume 快速命中');
|
||||
assert(idServiceContent.includes("'shopro_order'"), 'hasPurchased 校验有效订单');
|
||||
assert(idServiceContent.includes("'apply_refund_status', '<>', 2"), 'hasPurchased 排除全退订单');
|
||||
assert(idServiceContent.includes("identities[] = 'guest'"), '未买单时 identities 包含 guest');
|
||||
assert(idServiceContent.includes("identities[] = 'taster'"), '已买单时 identities 包含 taster');
|
||||
assert(idServiceContent.includes("$parts[] = $hasPurchased ? $tasterName : '游客'"), '无进阶身份时按买单状态显示品酒师或游客');
|
||||
assert(idServiceContent.includes("'is_guest' => $isGuest"), 'IdentityService::build 返回 is_guest');
|
||||
assert(idServiceContent.includes("'has_purchased' => $hasPurchased"), 'IdentityService::build 返回 has_purchased');
|
||||
assert(idServiceContent.includes("'is_guest' => $full['is_guest']"), 'IdentityService::attachProfile 返回 is_guest');
|
||||
console.log(' [✓ PASS] IdentityService.php 服务层买单检测与身份映射逻辑已就绪');
|
||||
|
||||
// 2. 验证 Agent.php 模型
|
||||
const agentModelPath = path.join(rootDir, 'application/admin/model/shopro/commission/Agent.php');
|
||||
assert(fs.existsSync(agentModelPath), 'Agent.php 模型文件存在');
|
||||
const agentModelContent = fs.readFileSync(agentModelPath, 'utf8');
|
||||
assert(agentModelContent.includes("'is_guest'"), 'Agent 模型 $append 数组包含 is_guest');
|
||||
assert(agentModelContent.includes("'has_purchased'"), 'Agent 模型 $append 数组包含 has_purchased');
|
||||
assert(agentModelContent.includes('function getIsGuestAttr'), 'Agent 模型包含 getIsGuestAttr 获取器');
|
||||
assert(agentModelContent.includes('function getHasPurchasedAttr'), 'Agent 模型包含 getHasPurchasedAttr 获取器');
|
||||
console.log(' [✓ PASS] Agent.php 模型 append 属性与获取器已就绪');
|
||||
|
||||
// 3. 验证 User.php 控制器 (API 与 Admin)
|
||||
const apiUserCtrlPath = path.join(rootDir, 'addons/shopro/controller/user/User.php');
|
||||
const apiUserContent = fs.readFileSync(apiUserCtrlPath, 'utf8');
|
||||
assert(apiUserContent.includes('attachProfile($agent, null, $user)'), 'api user/profile 传递 user 对象至 attachProfile');
|
||||
|
||||
const adminUserCtrlPath = path.join(rootDir, 'application/admin/controller/shopro/user/User.php');
|
||||
const adminUserContent = fs.readFileSync(adminUserCtrlPath, 'utf8');
|
||||
assert(adminUserContent.includes('attachProfile($agent, $rate, $user)'), '后台 User::index 传递 user 对象至 attachProfile');
|
||||
assert(adminUserContent.includes('attachProfile($agent, null, $user)'), '后台 User::detail 传递 user 对象至 attachProfile');
|
||||
console.log(' [✓ PASS] 后端控制器已打通 user 对象与 profile 联动');
|
||||
|
||||
// 4. 验证 Uniapp 前端组件
|
||||
const userCardPath = path.join(rootDir, 'uniapp/sheep/components/s-user-card/s-user-card.vue');
|
||||
assert(fs.existsSync(userCardPath), 's-user-card.vue 组件存在');
|
||||
const userCardContent = fs.readFileSync(userCardPath, 'utf8');
|
||||
assert(userCardContent.includes('v-if="isGuest" class="identity-tag guest-tag">游客</view>'), 's-user-card 包含游客标签模板渲染');
|
||||
assert(userCardContent.includes('const isGuest = computed('), 's-user-card 包含 isGuest 计算属性');
|
||||
assert(userCardContent.includes('.guest-tag {'), 's-user-card 包含 guest-tag 视觉样式');
|
||||
console.log(' [✓ PASS] s-user-card.vue 游客标签与品酒师徽章动态切换已就绪');
|
||||
|
||||
const commInfoPath = path.join(rootDir, 'uniapp/pages/commission/components/commission-info.vue');
|
||||
const commInfoContent = fs.readFileSync(commInfoPath, 'utf8');
|
||||
assert(commInfoContent.includes('userInfo.value?.identity_text'), 'commission-info.vue 优先使用 identity_text 展示游客/品酒师');
|
||||
console.log(' [✓ PASS] commission-info.vue 收益中心顶部身份文案已就绪');
|
||||
|
||||
const teamPath = path.join(rootDir, 'uniapp/pages/commission/team.vue');
|
||||
const teamContent = fs.readFileSync(teamPath, 'utf8');
|
||||
assert(teamContent.includes('item?.agent?.identity_text'), 'team.vue 格式化下级身份优先使用 identity_text');
|
||||
console.log(' [✓ PASS] team.vue 团队作战中心下级身份显示已就绪');
|
||||
|
||||
// 5. 模拟逻辑单元测试
|
||||
function simulateBuild(agent, user, orders) {
|
||||
const level = agent ? (agent.level || 0) : 0;
|
||||
const isVenue = agent ? (agent.is_venue || 0) : 0;
|
||||
const isCounty = agent ? (agent.is_county || 0) : 0;
|
||||
const isPartner = agent ? (agent.is_partner || 0) : 0;
|
||||
|
||||
let hasPurchased = false;
|
||||
if (user && parseFloat(user.total_consume || 0) > 0) {
|
||||
hasPurchased = true;
|
||||
} else if (orders && orders.length > 0) {
|
||||
hasPurchased = orders.some(o => ['paid', 'completed'].includes(o.status) && o.apply_refund_status !== 2);
|
||||
}
|
||||
|
||||
const isAdvanced = (level >= 2) || (isVenue === 1) || (isCounty === 1) || (isPartner === 1);
|
||||
const isGuest = (level === 1 && !hasPurchased && !isAdvanced);
|
||||
const identities = [];
|
||||
const parts = [];
|
||||
|
||||
if (level === 1) {
|
||||
if (hasPurchased) {
|
||||
identities.push('taster');
|
||||
} else if (!isAdvanced) {
|
||||
identities.push('guest');
|
||||
}
|
||||
} else if (level >= 2) {
|
||||
identities.push('maker');
|
||||
parts.push('创客V' + (level - 1));
|
||||
}
|
||||
|
||||
if (isVenue) {
|
||||
identities.push('venue');
|
||||
parts.push('酒道馆');
|
||||
}
|
||||
if (isCounty) {
|
||||
identities.push('county');
|
||||
parts.push('区县');
|
||||
}
|
||||
if (isPartner) {
|
||||
identities.push('partner');
|
||||
parts.push('合伙股东');
|
||||
}
|
||||
|
||||
if (parts.length === 0 && level === 1) {
|
||||
parts.push(hasPurchased ? '品酒师' : '游客');
|
||||
}
|
||||
|
||||
return {
|
||||
identities,
|
||||
identity_text: parts.join(' · ') || (isGuest ? '游客' : ''),
|
||||
is_guest: isGuest,
|
||||
has_purchased: hasPurchased,
|
||||
};
|
||||
}
|
||||
|
||||
// 模拟场景测试
|
||||
// 场景 1: 新用户注册,未买单 (0单,0消费)
|
||||
const res1 = simulateBuild({ level: 1, user_id: 101 }, { id: 101, total_consume: 0 }, []);
|
||||
assert.strictEqual(res1.identity_text, '游客', '场景1:新用户未买单 identity_text 应为游客');
|
||||
assert.strictEqual(res1.is_guest, true, '场景1:新用户未买单 is_guest 应为 true');
|
||||
assert(res1.identities.includes('guest'), '场景1:identities 包含 guest');
|
||||
assert(!res1.identities.includes('taster'), '场景1:identities 不含 taster');
|
||||
|
||||
// 场景 2: 购买 1 单已支付 (消费 1398)
|
||||
const res2 = simulateBuild({ level: 1, user_id: 102 }, { id: 102, total_consume: 1398 }, [{ status: 'paid', apply_refund_status: 0 }]);
|
||||
assert.strictEqual(res2.identity_text, '品酒师', '场景2:购买1单后 identity_text 应升级为品酒师');
|
||||
assert.strictEqual(res2.is_guest, false, '场景2:购买1单后 is_guest 应为 false');
|
||||
assert(res2.identities.includes('taster'), '场景2:identities 包含 taster');
|
||||
assert(!res2.identities.includes('guest'), '场景2:identities 不含 guest');
|
||||
|
||||
// 场景 3: 购买后发生全单退款完成 (apply_refund_status = 2)
|
||||
const res3 = simulateBuild({ level: 1, user_id: 103 }, { id: 103, total_consume: 0 }, [{ status: 'paid', apply_refund_status: 2 }]);
|
||||
assert.strictEqual(res3.identity_text, '游客', '场景3:全额退款后身份应回退为游客');
|
||||
assert.strictEqual(res3.is_guest, true, '场景3:全额退款后 is_guest 应为 true');
|
||||
|
||||
// 场景 4: 进阶创客角色不受影响 (level = 3 创客V2,未下单)
|
||||
const res4 = simulateBuild({ level: 3, user_id: 104 }, { id: 104, total_consume: 0 }, []);
|
||||
assert.strictEqual(res4.identity_text, '创客V2', '场景4:进阶创客未买单仍正常展示创客V2');
|
||||
assert.strictEqual(res4.is_guest, false, '场景4:进阶创客 is_guest 应为 false');
|
||||
|
||||
// 场景 5: 进阶酒道馆+区县角色不受影响
|
||||
const res5 = simulateBuild({ level: 1, is_venue: 1, is_county: 1, user_id: 105 }, { id: 105, total_consume: 0 }, []);
|
||||
assert.strictEqual(res5.identity_text, '酒道馆 · 区县', '场景5:酒道馆与区县正常展示');
|
||||
assert.strictEqual(res5.is_guest, false, '场景5:复合身份 is_guest 应为 false');
|
||||
|
||||
console.log(' [✓ PASS] 所有业务场景逻辑模拟测试 100% 通过!\n');
|
||||
console.log('========================================================');
|
||||
console.log(' 所有专项检查与验证均已完美通过!');
|
||||
console.log('========================================================');
|
||||
@@ -1,7 +1,7 @@
|
||||
/**
|
||||
* tests/partner/verify_madow_switch_spec.js
|
||||
*
|
||||
* 验证“我的马豆”页面马币市值指示胶囊后台开关:
|
||||
* 验证“我的dmepa”页面马币市值指示胶囊后台开关:
|
||||
* 1. 后端配置读取与默认值向后兼容逻辑验证
|
||||
* 2. 移动端前台显隐条件判定逻辑(开启、关闭、未配置)验证
|
||||
* 3. 接口数据契约与统一返回结构校验
|
||||
@@ -13,7 +13,7 @@ const path = require('path');
|
||||
const assert = require('assert');
|
||||
|
||||
console.log('====================================================');
|
||||
console.log(' 开始验证:“我的马豆”马币市值指示胶囊后台显隐开关');
|
||||
console.log(' 开始验证:“我的dmepa”马币市值指示胶囊后台显隐开关');
|
||||
console.log('====================================================\n');
|
||||
|
||||
let passCount = 0;
|
||||
|
||||
@@ -0,0 +1,174 @@
|
||||
/**
|
||||
* tests/partner/verify_remove_mabi_wording_spec.js
|
||||
*
|
||||
* 验证“我的dmepa”全链路移除“马币”二字:
|
||||
* 1. 移动端 madow.vue 指示胶囊文案对齐为「市值 1 dmepa ≈ xx 元」
|
||||
* 2. 移动端 madow.vue 抽屉弹窗默认文案与数据净化逻辑(不含“马币”)
|
||||
* 3. 服务端 PartnerConfig::madowRuleTitle 与 madowRules 彻底去除“马币”并具备脱敏自愈能力
|
||||
* 4. 后台 Config 控制器、后台视图 index.html、config.js 默认值与提示语彻底移除“马币”
|
||||
*/
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const assert = require('assert');
|
||||
|
||||
console.log('====================================================');
|
||||
console.log(' 验证:“我的dmepa”全链路彻底移除「马币」二字');
|
||||
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/commission/madow.vue
|
||||
runTest('uniapp/pages/commission/madow.vue 胶囊文案由「马币市值」调整为「市值」', () => {
|
||||
const file = path.join(rootDir, 'uniapp/pages/commission/madow.vue');
|
||||
assert.ok(fs.existsSync(file), 'madow.vue 存在');
|
||||
const content = fs.readFileSync(file, 'utf-8');
|
||||
|
||||
// 核心指示胶囊
|
||||
assert.ok(/市值\s*1\s*DMEPA\s*≈/i.test(content), '胶囊必须使用「市值 1 DMEPA ≈」');
|
||||
assert.ok(!content.includes('马币市值'), '胶囊中严禁出现「马币市值」');
|
||||
|
||||
// 顶栏红框文字去除验证
|
||||
assert.ok(content.includes('生态资产'), '必须保留「生态资产」标签');
|
||||
assert.ok(!content.includes('持有dmepa总量') && !content.includes('持有DMEPA总量'), '顶栏「持有DMEPA总量」文字已按需求彻底去掉');
|
||||
|
||||
// 默认规则弹窗标题
|
||||
assert.ok(content.includes("ruleTitle: '市值与增值规则'"), '默认弹窗标题必须为「市值与增值规则」');
|
||||
|
||||
// 默认规则内容
|
||||
assert.ok(content.includes("title: '市值动态增值'"), '默认规则条目必须为「市值动态增值」');
|
||||
|
||||
// 数据接收净化
|
||||
assert.ok(content.includes('.replace(/马币/g, \'\')'), 'getMadow 必须对动态规则标题和条目做去除「马币」净化');
|
||||
});
|
||||
|
||||
// 2. 验证 addons/shopro/service/partner/PartnerConfig.php
|
||||
runTest('addons/shopro/service/partner/PartnerConfig.php 规则方法彻底移除「马币」并脱敏净化', () => {
|
||||
const file = path.join(rootDir, 'addons/shopro/service/partner/PartnerConfig.php');
|
||||
assert.ok(fs.existsSync(file), 'PartnerConfig.php 存在');
|
||||
const content = fs.readFileSync(file, 'utf-8');
|
||||
|
||||
assert.ok(content.includes("self::get('partner_madow_rule_title', '市值与增值规则')"), '默认标题为「市值与增值规则」');
|
||||
assert.ok(content.includes("return str_replace('马币', '', $title);"), '标题输出时执行 str_replace 清除「马币」');
|
||||
assert.ok(content.includes("'title' => '市值动态增值'"), '默认规则条目包含「市值动态增值」');
|
||||
assert.ok(content.includes("str_replace('马币', '', $rule['title'])"), '条目输出时执行 str_replace 清除「马币」');
|
||||
});
|
||||
|
||||
// 3. 验证 application/admin/controller/shopro/Config.php
|
||||
runTest('application/admin/controller/shopro/Config.php 后台自愈默认值去除「马币」', () => {
|
||||
const file = path.join(rootDir, 'application/admin/controller/shopro/Config.php');
|
||||
assert.ok(fs.existsSync(file), 'Config.php 存在');
|
||||
const content = fs.readFileSync(file, 'utf-8');
|
||||
|
||||
assert.ok(content.includes("'partner_madow_rule_title' => '市值与增值规则'"), 'ruleDefaults 默认标题去除「马币」');
|
||||
assert.ok(content.includes("['title' => '市值动态增值'"), 'ruleDefaults 默认条目去除「马币」');
|
||||
});
|
||||
|
||||
// 4. 验证 application/admin/view/shopro/config/index.html
|
||||
runTest('application/admin/view/shopro/config/index.html 视图提示与占位符去除「马币」', () => {
|
||||
const file = path.join(rootDir, 'application/admin/view/shopro/config/index.html');
|
||||
assert.ok(fs.existsSync(file), 'index.html 存在');
|
||||
const content = fs.readFileSync(file, 'utf-8');
|
||||
|
||||
assert.ok(content.includes('市值 1 dmepa ≈ xxxx 元'), 'tip 提示更新为「市值 1 dmepa ≈ xxxx 元」');
|
||||
assert.ok(content.includes('placeholder="市值与增值规则"'), '输入框占位符更新为「市值与增值规则」');
|
||||
});
|
||||
|
||||
// 5. 验证 public/assets/js/backend/shopro/config.js
|
||||
runTest('public/assets/js/backend/shopro/config.js 默认值去除「马币」', () => {
|
||||
const file = path.join(rootDir, 'public/assets/js/backend/shopro/config.js');
|
||||
assert.ok(fs.existsSync(file), 'config.js 存在');
|
||||
const content = fs.readFileSync(file, 'utf-8');
|
||||
|
||||
assert.ok(content.includes("form.model.partner_madow_rule_title = '市值与增值规则'"), 'config.js 默认标题更新为「市值与增值规则」');
|
||||
assert.ok(content.includes("{ title: '市值动态增值'"), 'config.js 默认规则条目更新为「市值动态增值」');
|
||||
});
|
||||
|
||||
// 6. API 模拟测试:涵盖正常场景、业务失败场景、参数错误场景
|
||||
runTest('API 模拟测试:正常请求 partner/madow 接口数据结构与文案校验', () => {
|
||||
const mockSuccessResponse = {
|
||||
code: 1,
|
||||
msg: 'ok',
|
||||
data: {
|
||||
madow: '3993.0000',
|
||||
madow_rate: '0.00000858',
|
||||
madow_value: '0.03',
|
||||
show_madow_rate: 1,
|
||||
rule_title: '市值与增值规则',
|
||||
rules: [
|
||||
{ title: 'DMEPA获取与核算', desc: '平台合伙人与创客达成考核或完成订单后,系统将于次日 00:30 起统一参与每日DMEPA加权核算并自动分发。' },
|
||||
{ title: '市值动态增值', desc: '市值随全平台订单增加而上浮增值。全网完成订单总量越多,DMEPA单价所对应的市值越高,共享生态红利。' },
|
||||
{ title: '价值说明与权益', desc: '市值仅作为平台数字生态资产价值参考展示,不可直接兑换现金提现。具体权益按平台股东与合伙人章程执行。' }
|
||||
],
|
||||
list: []
|
||||
}
|
||||
};
|
||||
|
||||
assert.strictEqual(mockSuccessResponse.code, 1);
|
||||
assert.strictEqual(mockSuccessResponse.data.rule_title.includes('马币'), false, '返回的 rule_title 严禁包含马币');
|
||||
assert.strictEqual(mockSuccessResponse.data.rules[1].title.includes('马币'), false, '返回的 rules[1].title 严禁包含马币');
|
||||
assert.strictEqual(mockSuccessResponse.data.rules[1].desc.includes('马币'), false, '返回的 rules[1].desc 严禁包含马币');
|
||||
});
|
||||
|
||||
runTest('API 模拟测试:当服务端数据包含历史「马币」文案时,前端净化脱敏逻辑生效', () => {
|
||||
const dirtyData = {
|
||||
rule_title: '马币市值与增值规则',
|
||||
rules: [
|
||||
{ title: '马币市值动态增值', desc: '马币市值随全平台订单增加而上浮增值。' }
|
||||
]
|
||||
};
|
||||
|
||||
const sanitizedTitle = String(dirtyData.rule_title).replace(/马币/g, '');
|
||||
const sanitizedRules = dirtyData.rules.map(item => ({
|
||||
...item,
|
||||
title: String(item.title || '').replace(/马币/g, ''),
|
||||
desc: String(item.desc || '').replace(/马币/g, '')
|
||||
}));
|
||||
|
||||
assert.strictEqual(sanitizedTitle, '市值与增值规则');
|
||||
assert.strictEqual(sanitizedRules[0].title, '市值动态增值');
|
||||
assert.strictEqual(sanitizedRules[0].desc, '市值随全平台订单增加而上浮增值。');
|
||||
});
|
||||
|
||||
runTest('API 模拟测试:业务拒绝场景(未登录/无权限)统一响应封装', () => {
|
||||
const mockAuthFailResponse = {
|
||||
code: 0,
|
||||
msg: '请先登录后查看DMEPA资产',
|
||||
data: null
|
||||
};
|
||||
assert.strictEqual(mockAuthFailResponse.code, 0);
|
||||
assert.strictEqual(typeof mockAuthFailResponse.msg, 'string');
|
||||
});
|
||||
|
||||
runTest('API 模拟测试:参数错误场景(page为非法字符串)边界处理', () => {
|
||||
const sanitizePage = (inputPage) => {
|
||||
const p = parseInt(inputPage, 10);
|
||||
return isNaN(p) || p < 1 ? 1 : p;
|
||||
};
|
||||
assert.strictEqual(sanitizePage('invalid'), 1);
|
||||
assert.strictEqual(sanitizePage('-5'), 1);
|
||||
assert.strictEqual(sanitizePage('3'), 3);
|
||||
});
|
||||
|
||||
console.log('\n====================================================');
|
||||
console.log(`测试完成: 成功 ${passCount} 项, 失败 ${failCount} 项`);
|
||||
console.log('====================================================');
|
||||
|
||||
if (failCount > 0) {
|
||||
process.exit(1);
|
||||
}
|
||||
@@ -105,7 +105,9 @@ assert(fs.existsSync(moneyVuePath), 'money.vue 文件存在');
|
||||
if (fs.existsSync(moneyVuePath)) {
|
||||
const content = fs.readFileSync(moneyVuePath, 'utf-8');
|
||||
assert(content.includes("name: '收益分成'") && content.includes("value: 'reward'"), 'money.vue 的 tabMaps 增加「收益分成」筛选');
|
||||
assert(content.includes('收益分成全额入账'), '动态指示胶囊说明收益分成全额入账可直接抵扣');
|
||||
assert(content.includes('tag-badge') && content.includes('现金资产'), '保留现金资产专属标签');
|
||||
assert(!content.includes('账户钱包余额'), '已成功移除「账户钱包余额」文案');
|
||||
assert(!content.includes('线上充值与收益分成全额入账'), '已成功移除「线上充值与收益分成全额入账」胶囊文案');
|
||||
}
|
||||
|
||||
// ----------------------------------------------------
|
||||
|
||||
在新工单中引用
屏蔽一个用户