217 行
11 KiB
JavaScript
217 行
11 KiB
JavaScript
/**
|
||
* 测试用例:验证团队中心移除二级间推并支持一级直推多层级下钻功能
|
||
* 覆盖范围:
|
||
* 1. 后端 Agent.php team() 接口:全面移除二级查询分支,默认一级直推
|
||
* 2. 后端防越权链条鉴权机制:追溯 parent_user_id 防止跨团队越权下钻
|
||
* 3. 前端 team.vue 界面:彻底清除“二级间推”、“二级成员”概念
|
||
* 4. 前端下钻状态管理:drillStack 栈驱动、currentDrillUser 计算属性与 API 参数透传
|
||
* 5. 前端导航与交互:面包屑导航栏、返回上一层、回到根团队、卡片下钻按钮、指标下钻与抽屉下钻卡片
|
||
* 6. 完整业务流程模拟:多级下钻、路径回溯与越权拦截测试
|
||
*/
|
||
|
||
const fs = require('fs');
|
||
const path = require('path');
|
||
const assert = require('assert');
|
||
|
||
console.log('=== 开始执行团队中心一级直推与多层级下钻自动化测试 ===\n');
|
||
|
||
// 1. 验证后端 Agent.php 接口改造
|
||
const agentPhpPath = path.resolve(__dirname, '../addons/shopro/controller/commission/Agent.php');
|
||
assert(fs.existsSync(agentPhpPath), 'Agent.php 文件必须存在');
|
||
const agentPhpContent = fs.readFileSync(agentPhpPath, 'utf-8');
|
||
|
||
// 1.1 检查下钻参数与鉴权
|
||
assert(agentPhpContent.includes("drill_user_id"), 'Agent.php 必须接收 drill_user_id 参数');
|
||
assert(agentPhpContent.includes("isDownline = true"), 'Agent.php 必须实现父级链条归属鉴权');
|
||
assert(agentPhpContent.includes("无权查看非直属团队成员信息"), 'Agent.php 必须对非团队成员下钻进行严格权限拦截');
|
||
assert(agentPhpContent.includes("drill_user"), 'Agent.php 必须在返回结果中包含下钻用户信息 drill_user');
|
||
|
||
// 1.2 检查彻底移除二级间推分支
|
||
assert(!agentPhpContent.includes("$levelType == '2'"), 'Agent.php 必须彻底移除 levelType == 2 的二级间推分支');
|
||
assert(agentPhpContent.includes("where('u.parent_user_id', $targetUserId)"), 'Agent.php 必须采用直推一级关系查询');
|
||
|
||
console.log('✅ 测试点 1 通过: 后端 Agent.php team() 接口直推与下钻链条鉴权逻辑正确');
|
||
|
||
// 2. 模拟后端向上追溯防越权鉴权算法测试
|
||
function verifyDownlinePermission(loginAgentId, targetUserId, userHierarchyMap) {
|
||
if (targetUserId === loginAgentId) return true; // 查看自己
|
||
let curr = userHierarchyMap[targetUserId];
|
||
if (!curr) return false; // 用户不存在
|
||
|
||
let depth = 0;
|
||
let currParentId = curr.parent_user_id;
|
||
while (currParentId > 0 && depth < 30) {
|
||
if (currParentId === loginAgentId) {
|
||
return true; // 确认是自己的下属团队链条
|
||
}
|
||
const parentUser = userHierarchyMap[currParentId];
|
||
if (!parentUser) break;
|
||
currParentId = parentUser.parent_user_id || 0;
|
||
depth++;
|
||
}
|
||
return false;
|
||
}
|
||
|
||
// 构建模拟团队关系树:
|
||
// 登录用户: 100
|
||
// 100 -> 101 (直推1级)
|
||
// 101 -> 102 (直推下级,相对100是下属)
|
||
// 102 -> 103 (继续深层直推下级)
|
||
// 外部人员: 200 -> 201 (与100无关联)
|
||
const mockUserDb = {
|
||
100: { id: 100, parent_user_id: 0, nickname: '根用户' },
|
||
101: { id: 101, parent_user_id: 100, nickname: '直推A' },
|
||
102: { id: 102, parent_user_id: 101, nickname: '直推A的下级B' },
|
||
103: { id: 103, parent_user_id: 102, nickname: '下级B的下级C' },
|
||
200: { id: 200, parent_user_id: 0, nickname: '其他团队长' },
|
||
201: { id: 201, parent_user_id: 200, nickname: '其他团队成员' },
|
||
};
|
||
|
||
// 验证正常直属下钻
|
||
assert.strictEqual(verifyDownlinePermission(100, 101, mockUserDb), true, '登录用户查看自己的一级直推 101 必须鉴权通过');
|
||
assert.strictEqual(verifyDownlinePermission(100, 102, mockUserDb), true, '登录用户下钻二级成员 102 必须鉴权通过');
|
||
assert.strictEqual(verifyDownlinePermission(100, 103, mockUserDb), true, '登录用户多级下钻三级成员 103 必须鉴权通过');
|
||
|
||
// 验证越权拦截
|
||
assert.strictEqual(verifyDownlinePermission(100, 200, mockUserDb), false, '尝试越权下钻其他团队长 200 必须被拦截');
|
||
assert.strictEqual(verifyDownlinePermission(100, 201, mockUserDb), false, '尝试越权下钻其他团队成员 201 必须被拦截');
|
||
assert.strictEqual(verifyDownlinePermission(100, 9999, mockUserDb), false, '下钻不存在的用户必须被拦截');
|
||
|
||
console.log('✅ 测试点 2 通过: 防越权追溯鉴权算法通过(正常下钻放行、越权调用均被准确拦截)');
|
||
|
||
// 3. 验证前端 team.vue 页面
|
||
const teamVuePath = path.resolve(__dirname, '../uniapp/pages/commission/team.vue');
|
||
assert(fs.existsSync(teamVuePath), 'team.vue 文件必须存在');
|
||
const teamVueContent = fs.readFileSync(teamVuePath, 'utf-8');
|
||
|
||
// 3.1 验证彻底移除“二级”文本
|
||
const hasSecondaryText = teamVueContent.includes('二级间推') || teamVueContent.includes('二级成员');
|
||
assert.strictEqual(hasSecondaryText, false, 'team.vue 页面中严禁存在任何“二级间推”或“二级成员”字样');
|
||
|
||
// 3.2 验证移除 levelTabs
|
||
assert(!teamVueContent.includes('currentLevelTab'), 'team.vue 必须移除 currentLevelTab 状态');
|
||
assert(!teamVueContent.includes('levelTabs'), 'team.vue 必须移除 levelTabs 变量');
|
||
|
||
// 3.3 验证下钻状态管理
|
||
assert(teamVueContent.includes('drillStack: []'), 'team.vue 必须声明 drillStack 状态');
|
||
assert(teamVueContent.includes('currentDrillUser = computed'), 'team.vue 必须包含 currentDrillUser 计算属性');
|
||
assert(teamVueContent.includes('onDrillDown'), 'team.vue 必须包含 onDrillDown 下钻方法');
|
||
assert(teamVueContent.includes('onDrillBack'), 'team.vue 必须包含 onDrillBack 返回上一层方法');
|
||
assert(teamVueContent.includes('onDrillRoot'), 'team.vue 必须包含 onDrillRoot 回到根团队方法');
|
||
assert(teamVueContent.includes('onJumpDrillIndex'), 'team.vue 必须包含 onJumpDrillIndex 面包屑跳跃方法');
|
||
assert(teamVueContent.includes('onDrillFromDrawer'), 'team.vue 必须包含从抽屉中下钻的方法');
|
||
assert(teamVueContent.includes('drill_user_id: currentDrillUser.value'), 'team.vue getTeamList 必须传递 drill_user_id');
|
||
|
||
// 3.4 验证下钻 UI 元素渲染
|
||
assert(teamVueContent.includes('drill-nav-bar'), 'team.vue 必须渲染 drill-nav-bar 下钻导航栏');
|
||
assert(teamVueContent.includes('drill-back-btn'), 'team.vue 必须包含返回上一层按钮');
|
||
assert(teamVueContent.includes('drill-breadcrumb'), 'team.vue 必须包含层级面包屑路径');
|
||
assert(teamVueContent.includes('btn-drill'), 'team.vue 成员卡片必须包含下钻团队按钮');
|
||
assert(teamVueContent.includes('drawer-drill-card'), 'team.vue 详情抽屉必须包含下钻团队卡片');
|
||
|
||
// 3.5 验证顶部卡片只展示直推人数与团队总人数,且无任何一级/二级
|
||
assert(teamVueContent.includes('直推人数(人)'), 'team.vue 顶部必须展示直推人数(人)卡片');
|
||
assert(teamVueContent.includes('团队总人数(人)'), 'team.vue 顶部必须展示团队总人数(人)卡片');
|
||
assert(teamVueContent.includes('getDirectCount()'), 'team.vue 必须调用 getDirectCount 计算直推人数');
|
||
assert(teamVueContent.includes('getTeamCount()'), 'team.vue 必须调用 getTeamCount 计算团队总人数');
|
||
assert(!teamVueContent.includes('一级直推'), 'team.vue 顶部卡片严禁出现“一级直推”字样');
|
||
assert(!teamVueContent.includes('一级成员'), 'team.vue 顶部卡片严禁出现“一级成员”字样');
|
||
|
||
console.log('✅ 测试点 3 通过: 前端 team.vue 状态管理、下钻面包屑导航与顶部纯直推/团队总人数卡片规范');
|
||
|
||
// 4. 模拟前端下钻堆栈业务状态机流转测试
|
||
class MockTeamDrillController {
|
||
constructor() {
|
||
this.drillStack = [];
|
||
this.currentFilter = 'all';
|
||
this.keyword = '';
|
||
this.lastParams = null;
|
||
}
|
||
|
||
get currentDrillUser() {
|
||
if (this.drillStack.length === 0) return null;
|
||
return this.drillStack[this.drillStack.length - 1];
|
||
}
|
||
|
||
getTeamList(page = 1) {
|
||
this.lastParams = {
|
||
page,
|
||
drill_user_id: this.currentDrillUser ? this.currentDrillUser.id : 0,
|
||
filter_type: this.currentFilter,
|
||
keyword: this.keyword,
|
||
};
|
||
return this.lastParams;
|
||
}
|
||
|
||
onDrillDown(member) {
|
||
if (!member || !member.id) return;
|
||
if (this.drillStack.some((item) => item.id === member.id)) return;
|
||
this.drillStack.push({
|
||
id: member.id,
|
||
nickname: member.nickname || '直推成员',
|
||
child_user_count_1: member.child_user_count_1 || 0,
|
||
});
|
||
this.keyword = '';
|
||
return this.getTeamList(1);
|
||
}
|
||
|
||
onDrillBack() {
|
||
if (this.drillStack.length === 0) return;
|
||
this.drillStack.pop();
|
||
this.keyword = '';
|
||
return this.getTeamList(1);
|
||
}
|
||
|
||
onDrillRoot() {
|
||
if (this.drillStack.length === 0) return;
|
||
this.drillStack = [];
|
||
this.keyword = '';
|
||
return this.getTeamList(1);
|
||
}
|
||
|
||
onJumpDrillIndex(index) {
|
||
if (index >= this.drillStack.length - 1) return;
|
||
this.drillStack = this.drillStack.slice(0, index + 1);
|
||
this.keyword = '';
|
||
return this.getTeamList(1);
|
||
}
|
||
}
|
||
|
||
const controller = new MockTeamDrillController();
|
||
|
||
// 步骤 1: 初始化根列表
|
||
const initReq = controller.getTeamList(1);
|
||
assert.strictEqual(initReq.drill_user_id, 0, '根列表请求 drill_user_id 必须为 0');
|
||
assert.strictEqual(controller.currentDrillUser, null, '根目录下 currentDrillUser 必须为 null');
|
||
|
||
// 步骤 2: 点击成员 101 下钻
|
||
const drill1Req = controller.onDrillDown({ id: 101, nickname: '张三', child_user_count_1: 5 });
|
||
assert.strictEqual(drill1Req.drill_user_id, 101, '一级下钻请求 drill_user_id 必须为 101');
|
||
assert.strictEqual(controller.drillStack.length, 1, '下钻栈深度应为 1');
|
||
assert.strictEqual(controller.currentDrillUser.nickname, '张三', '当前下钻用户应为张三');
|
||
|
||
// 步骤 3: 在 101 名下继续点击成员 102 下钻
|
||
const drill2Req = controller.onDrillDown({ id: 102, nickname: '李四', child_user_count_1: 2 });
|
||
assert.strictEqual(drill2Req.drill_user_id, 102, '二级下钻请求 drill_user_id 必须为 102');
|
||
assert.strictEqual(controller.drillStack.length, 2, '下钻栈深度应为 2');
|
||
assert.strictEqual(controller.currentDrillUser.nickname, '李四', '当前下钻用户应为李四');
|
||
|
||
// 步骤 4: 点击“返回上一层”
|
||
const backReq = controller.onDrillBack();
|
||
assert.strictEqual(backReq.drill_user_id, 101, '返回上一层后请求 drill_user_id 必须回到 101');
|
||
assert.strictEqual(controller.drillStack.length, 1, '返回后下钻栈深度应为 1');
|
||
assert.strictEqual(controller.currentDrillUser.nickname, '张三', '当前下钻用户应恢复为张三');
|
||
|
||
// 步骤 5: 再次深层下钻并一键“回到根团队”
|
||
controller.onDrillDown({ id: 102, nickname: '李四' });
|
||
controller.onDrillDown({ id: 103, nickname: '王五' });
|
||
assert.strictEqual(controller.drillStack.length, 3);
|
||
const rootReq = controller.onDrillRoot();
|
||
assert.strictEqual(rootReq.drill_user_id, 0, '一键回到根团队后 drill_user_id 必须为 0');
|
||
assert.strictEqual(controller.drillStack.length, 0, '下钻栈必须被清空');
|
||
assert.strictEqual(controller.currentDrillUser, null, '当前下钻用户必须为 null');
|
||
|
||
console.log('✅ 测试点 4 通过: 前端下钻堆栈与状态流转业务逻辑全部验证通过');
|
||
|
||
console.log('\n🎉 所有测试均通过!团队中心一级直推与多层级下钻功能验证完毕!');
|