文件
Henglianshutong/tests/partner/verify_guest_to_taster_identity_spec.js

157 行
8.9 KiB
JavaScript

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

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

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('========================================================');