import { AuthRoleManager, UserRoleFlag, WorkMode, CertStatus } from '../src/core/auth/auth-role.manager'; import { CompanionOrderService } from '../src/core/order/companion-order.service'; import { ActivityService, ActivityPayType } from '../src/core/activity/activity.service'; import { OrderStatus } from '../src/core/order/order-state-machine'; import { CompanionTrack, ExpenseBurdenType } from '../src/core/companion/service-category'; import * as fs from 'fs'; import * as path from 'path'; describe('RBAC Permission Architecture (TDD 全流程多角色权限与越权防御套件)', () => { let roleManager: AuthRoleManager; let orderService: CompanionOrderService; let activityService: ActivityService; beforeEach(() => { roleManager = new AuthRoleManager(); orderService = new CompanionOrderService(); activityService = new ActivityService(); }); describe('1. 四大核心角色体系与权限位图验证', () => { it('角色位图常量定义完整且互斥', () => { expect(UserRoleFlag.PLAYER).toBe(1); expect(UserRoleFlag.EXPERT).toBe(2); expect(UserRoleFlag.HOST).toBe(4); expect(UserRoleFlag.ADMIN).toBe(8); }); it('普通注册用户默认仅拥有玩家身份 (PLAYER)', () => { const user = roleManager.registerUser({ openid: 'wx_user_1', nickname: '玩家小明' }); expect(user.roleFlags).toBe(UserRoleFlag.PLAYER); expect(roleManager.isPlayer(user.id)).toBe(true); expect(roleManager.isExpert(user.id)).toBe(false); expect(roleManager.isHost(user.id)).toBe(false); expect(roleManager.isAdmin(user.id)).toBe(false); expect(user.currentMode).toBe(WorkMode.PLAYER); }); it('平台管理员账号具备 ADMIN 与 PLAYER 复合权限', () => { const admin = roleManager.registerAdmin({ openid: 'wx_admin_1', nickname: '总控管理员' }); expect(roleManager.isAdmin(admin.id)).toBe(true); expect(roleManager.isPlayer(admin.id)).toBe(true); expect(admin.roleFlags & UserRoleFlag.ADMIN).toBeTruthy(); }); it('支持动态授予与撤销角色位图权限', () => { const user = roleManager.registerUser({ openid: 'wx_user_2', nickname: '多面手' }); expect(roleManager.isHost(user.id)).toBe(false); // 授予车队房主 HOST 权限 roleManager.grantRole(user.id, UserRoleFlag.HOST); expect(roleManager.isHost(user.id)).toBe(true); // 撤销 HOST 权限 roleManager.revokeRole(user.id, UserRoleFlag.HOST); expect(roleManager.isHost(user.id)).toBe(false); }); }); describe('2. 角色一:普通买家/玩家 (PLAYER) 业务与越权防御', () => { let buyer: any; let expert: any; let stranger: any; beforeEach(() => { buyer = roleManager.registerUser({ openid: 'wx_buyer', nickname: '下单老板' }); expert = roleManager.registerUser({ openid: 'wx_expert', nickname: '认证大神' }); stranger = roleManager.registerUser({ openid: 'wx_stranger', nickname: '路人用户' }); // 认证大神 roleManager.submitExpertCert(expert.id, { realName: '李大神', idCardNo: '110101199001011234' }); roleManager.approveExpertCert(expert.id); roleManager.switchMode(expert.id, WorkMode.EXPERT); }); it('买家正常创建并支付线上订单', () => { const order = orderService.createOrder({ buyerId: buyer.id, expertId: expert.id, track: CompanionTrack.ONLINE_GAME, serviceTitle: '王者荣耀五排上分', hours: 2, totalAmountCents: 6000, gameDetails: { gameName: '王者荣耀', gameId: '峡谷第一野王', serverDistrict: '微信区', }, }); expect(order.status).toBe(OrderStatus.PENDING_PAYMENT); // 只有买家有权付款 orderService.payOrder(order.id, buyer.id); expect(order.status).toBe(OrderStatus.PENDING_ACCEPT); }); it('越权拦截:非下单买家尝试支付订单应被拒绝', () => { const order = orderService.createOrder({ buyerId: buyer.id, expertId: expert.id, track: CompanionTrack.ONLINE_GAME, serviceTitle: '王者双排', hours: 1, totalAmountCents: 3000, }); expect(() => { orderService.payOrder(order.id, stranger.id); }).toThrow('非下单买家无权支付该订单'); }); it('待付款状态下买家可取消订单,非买家取消应被拒绝', () => { const order = orderService.createOrder({ buyerId: buyer.id, expertId: expert.id, track: CompanionTrack.ONLINE_GAME, serviceTitle: '王者双排', hours: 1, totalAmountCents: 3000, }); // 非买家尝试取消 expect(() => { orderService.buyerCancel(order.id, stranger.id); }).toThrow('只有下单买家有权取消订单'); // 买家正常取消 orderService.buyerCancel(order.id, buyer.id); expect(order.status).toBe(OrderStatus.CANCELLED); }); it('待确认状态下买家正常确认完成验收,非买家验收应被拒绝', () => { const order = orderService.createOrder({ buyerId: buyer.id, expertId: expert.id, track: CompanionTrack.ONLINE_GAME, serviceTitle: '排位服务', hours: 1, totalAmountCents: 3000, }); orderService.payOrder(order.id, buyer.id); orderService.expertAccept(order.id, expert.id, roleManager); orderService.expertApplyFinish(order.id, expert.id); expect(order.status).toBe(OrderStatus.PENDING_CONFIRM); // 非买家尝试确认验收 expect(() => { orderService.buyerConfirm(order.id, stranger.id); }).toThrow('只有下单买家有权确认完成验收'); // 买家确认完成 orderService.buyerConfirm(order.id, buyer.id); expect(order.status).toBe(OrderStatus.COMPLETED); }); it('待确认状态下买家发起争议仲裁,非买家发起仲裁应被拒绝', () => { const order = orderService.createOrder({ buyerId: buyer.id, expertId: expert.id, track: CompanionTrack.ONLINE_GAME, serviceTitle: '排位服务', hours: 1, totalAmountCents: 3000, }); orderService.payOrder(order.id, buyer.id); orderService.expertAccept(order.id, expert.id, roleManager); orderService.expertApplyFinish(order.id, expert.id); // 非买家尝试发起争议 expect(() => { orderService.buyerDispute(order.id, stranger.id, '大神挂机'); }).toThrow('只有下单买家有权发起争议仲裁'); // 买家发起争议 orderService.buyerDispute(order.id, buyer.id, '大神迟到并且对局中挂机'); expect(order.status).toBe(OrderStatus.DISPUTED); expect(order.disputeReason).toBe('大神迟到并且对局中挂机'); }); it('越权拦截:买家不可越权执行平台管理员争议仲裁', () => { const order = orderService.createOrder({ buyerId: buyer.id, expertId: expert.id, track: CompanionTrack.ONLINE_GAME, serviceTitle: '排位服务', hours: 1, totalAmountCents: 3000, }); orderService.payOrder(order.id, buyer.id); orderService.expertAccept(order.id, expert.id, roleManager); orderService.expertApplyFinish(order.id, expert.id); orderService.buyerDispute(order.id, buyer.id, '要求全额退款'); // 买家尝试越权自行仲裁退款 expect(() => { orderService.arbitrate({ orderId: order.id, operatorAdminId: buyer.id, decision: 'REFUND', roleManager, }); }).toThrow(); }); }); describe('3. 角色二:陪玩大神/陪伴师 (EXPERT) 认证全流程与越权防御', () => { let applicant: any; let normalBuyer: any; let admin: any; beforeEach(() => { applicant = roleManager.registerUser({ openid: 'wx_app_1', nickname: '申请大神选手' }); normalBuyer = roleManager.registerUser({ openid: 'wx_buy_2', nickname: '买家阿强' }); admin = roleManager.registerAdmin({ openid: 'wx_admin_2', nickname: '平台运营审核员' }); }); it('申请实名认证成功并生成 PENDING 记录', () => { const cert = roleManager.submitExpertCert(applicant.id, { realName: '王小萌', idCardNo: '110101200001011111', }); expect(cert.status).toBe(CertStatus.PENDING); expect(roleManager.getCertStatus(applicant.id)).toBe(CertStatus.PENDING); expect(roleManager.isExpert(applicant.id)).toBe(false); }); it('越权拦截:普通买家尝试审核大神认证申请应被抛出权限异常拒绝', () => { roleManager.submitExpertCert(applicant.id, { realName: '王小萌', idCardNo: '110101200001011111', }); expect(() => { roleManager.approveExpertCert(applicant.id, normalBuyer.id); }).toThrow('只有平台管理员有权审核大神认证申请'); expect(() => { roleManager.rejectExpertCert(applicant.id, '资料不合格', normalBuyer.id); }).toThrow('只有平台管理员有权审核大神认证申请'); }); it('平台管理员成功审核通过实名认证,角色位图升级并支持接单', () => { roleManager.submitExpertCert(applicant.id, { realName: '王小萌', idCardNo: '110101200001011111', }); const cert = roleManager.approveExpertCert(applicant.id, admin.id); expect(cert.status).toBe(CertStatus.APPROVED); expect(cert.auditedBy).toBe(admin.id); expect(roleManager.isExpert(applicant.id)).toBe(true); // 允许切换到接单模式 roleManager.switchMode(applicant.id, WorkMode.EXPERT); expect(roleManager.getUser(applicant.id).currentMode).toBe(WorkMode.EXPERT); }); it('平台管理员驳回实名认证并记录驳回理由', () => { roleManager.submitExpertCert(applicant.id, { realName: '王小萌', idCardNo: '110101200001011111', }); const cert = roleManager.rejectExpertCert(applicant.id, '身份证照片模糊不清晰', admin.id); expect(cert.status).toBe(CertStatus.REJECTED); expect(cert.rejectReason).toBe('身份证照片模糊不清晰'); expect(roleManager.isExpert(applicant.id)).toBe(false); }); it('越权拦截:未认证用户尝试接单应被严格拦截拒绝', () => { const order = orderService.createOrder({ buyerId: normalBuyer.id, expertId: applicant.id, // 尚未认证 track: CompanionTrack.ONLINE_GAME, serviceTitle: '游戏陪玩', hours: 1, totalAmountCents: 3500, }); orderService.payOrder(order.id, normalBuyer.id); expect(() => { orderService.expertAccept(order.id, applicant.id, roleManager); }).toThrow('未认证大神无法接单'); }); it('同城线下订单生成 4 位现场核销码,非接单大神核销被拒绝,接单大神现场核销闭环', () => { // 认证大神 roleManager.submitExpertCert(applicant.id, { realName: '王小萌', idCardNo: '110101200001011111' }); roleManager.approveExpertCert(applicant.id, admin.id); const order = orderService.createOrder({ buyerId: normalBuyer.id, expertId: applicant.id, track: CompanionTrack.OFFLINE_MEAL, serviceTitle: '三里屯火锅探店', hours: 2, totalAmountCents: 12000, offlineDetails: { city: '北京市', venueName: '朝阳大悦城海底捞', meetTime: '今晚 19:30', expenseBurden: ExpenseBurdenType.AA_SHARE, }, }); orderService.payOrder(order.id, normalBuyer.id); orderService.expertAccept(order.id, applicant.id, roleManager); expect(order.status).toBe(OrderStatus.PENDING_CONFIRM); expect(order.offlineVerifyCode).toMatch(/^\d{4}$/); // 非接单大神越权核销 expect(() => { orderService.verifyOfflineCheckin({ orderId: order.id, operatorExpertId: 9999, // 外部非法大神 verifyCode: order.offlineVerifyCode!, }); }).toThrow('无权核销该订单'); // 接单大神凭正确 4 位码核销成功进入服务中 const checkinRes = orderService.verifyOfflineCheckin({ orderId: order.id, operatorExpertId: applicant.id, verifyCode: order.offlineVerifyCode!, }); expect(checkinRes.success).toBe(true); expect(order.status).toBe(OrderStatus.IN_SERVICE); // 防重校验:不可重复核销 expect(() => { orderService.verifyOfflineCheckin({ orderId: order.id, operatorExpertId: applicant.id, verifyCode: order.offlineVerifyCode!, }); }).toThrow('该订单已完成现场核销签到'); }); it('接单大神拒单流转为已退款,非指定接单大神拒单应被拦截', () => { roleManager.submitExpertCert(applicant.id, { realName: '王小萌', idCardNo: '110101200001011111' }); roleManager.approveExpertCert(applicant.id, admin.id); const order = orderService.createOrder({ buyerId: normalBuyer.id, expertId: applicant.id, track: CompanionTrack.ONLINE_GAME, serviceTitle: '游戏陪练', hours: 1, totalAmountCents: 3500, }); orderService.payOrder(order.id, normalBuyer.id); // 非指定接单大神尝试拒单 expect(() => { orderService.expertReject(order.id, 9999); }).toThrow('只有指定接单大神有权拒单'); // 指定接单大神拒单 orderService.expertReject(order.id, applicant.id, '时间冲突'); expect(order.status).toBe(OrderStatus.REFUNDED); }); it('越权拦截:大神不可越权执行平台管理员争议仲裁', () => { roleManager.submitExpertCert(applicant.id, { realName: '王小萌', idCardNo: '110101200001011111' }); roleManager.approveExpertCert(applicant.id, admin.id); const order = orderService.createOrder({ buyerId: normalBuyer.id, expertId: applicant.id, track: CompanionTrack.ONLINE_GAME, serviceTitle: '游戏陪练', hours: 1, totalAmountCents: 3500, }); orderService.payOrder(order.id, normalBuyer.id); orderService.expertAccept(order.id, applicant.id, roleManager); orderService.expertApplyFinish(order.id, applicant.id); orderService.buyerDispute(order.id, normalBuyer.id, '未达段位'); // 大神尝试越权自行结单打款 expect(() => { orderService.arbitrate({ orderId: order.id, operatorAdminId: applicant.id, decision: 'RELEASE', roleManager, }); }).toThrow(); }); }); describe('4. 角色三:车队房主/局长 (HOST) 权限闭环与越权防御', () => { let host: any; let participant1: any; let participant2: any; let outsider: any; let admin: any; beforeEach(() => { host = roleManager.registerUser({ openid: 'wx_host', nickname: '局长房主' }); participant1 = roleManager.registerUser({ openid: 'wx_p1', nickname: '搭子甲' }); participant2 = roleManager.registerUser({ openid: 'wx_p2', nickname: '搭子乙' }); outsider = roleManager.registerUser({ openid: 'wx_out', nickname: '无关人员' }); admin = roleManager.registerAdmin({ openid: 'wx_admin_3', nickname: '监管运营' }); }); it('房主发起组局自动获得 HOST 权限', () => { const act = activityService.createActivity({ creatorId: host.id, title: '【周末同城】火锅探店4人局', gameCategory: '同城陪吃', payType: ActivityPayType.AA_PAY, aaFeeCents: 5000, maxParticipants: 4, roleManager, }); expect(act.creatorId).toBe(host.id); expect(roleManager.isHost(host.id)).toBe(true); }); it('房主动态调价加赏差额全员补齐,非房主调价抛出权限异常', () => { const act = activityService.createActivity({ creatorId: host.id, title: '【加赏急招】电竞开黑车队', gameCategory: '热门电竞', payType: ActivityPayType.PROJECT_PAY, perPersonFeeCents: 5000, maxParticipants: 3, roleManager, }); const p1 = activityService.joinActivity(act.id, { userId: participant1.id, payAmountCents: 5000 }); // 非房主尝试调价 expect(() => { activityService.adjustActivityPrice({ activityId: act.id, operatorId: outsider.id, newPriceCents: 8000, }); }).toThrow('只有发起人有权调整活动出价'); // 房主成功调价至80元/人 const res = activityService.adjustActivityPrice({ activityId: act.id, operatorId: host.id, newPriceCents: 8000, }); expect(res.priceDiffCents).toBe(3000); expect(p1.payAmountCents).toBe(8000); }); it('房主使用 4 位核销码核销到场搭子,非房主核销被拦截拒绝', () => { const act = activityService.createActivity({ creatorId: host.id, title: '同城剧本杀局', gameCategory: '同城陪玩', payType: ActivityPayType.AA_PAY, aaFeeCents: 6000, maxParticipants: 4, roleManager, }); const p = activityService.joinActivity(act.id, { userId: participant1.id, payAmountCents: 6000 }); // 非房主越权核销搭子门票 expect(() => { activityService.verifyCheckin({ activityId: act.id, operatorId: outsider.id, verifyCode: p.verifyCode, }); }).toThrow('无权核销该活动的核销码'); // 房主核销成功 const checkRes = activityService.verifyCheckin({ activityId: act.id, operatorId: host.id, verifyCode: p.verifyCode, }); expect(checkRes.success).toBe(true); expect(checkRes.participant.checkinStatus).toBe(true); }); it('房主解散组局并全额原路退款已报名搭子,非房主解散被拦截', () => { const act = activityService.createActivity({ creatorId: host.id, title: '同城KTV狂欢局', gameCategory: '同城陪唱', payType: ActivityPayType.AA_PAY, aaFeeCents: 7500, maxParticipants: 4, roleManager, }); activityService.joinActivity(act.id, { userId: participant1.id, payAmountCents: 7500 }); activityService.joinActivity(act.id, { userId: participant2.id, payAmountCents: 7500 }); // 非房主尝试解散 expect(() => { activityService.cancelActivity({ activityId: act.id, operatorId: outsider.id, reason: '恶意解散', }); }).toThrow('只有发起人有权解散此组局'); // 房主正常解散 const cancelRes = activityService.cancelActivity({ activityId: act.id, operatorId: host.id, reason: '突发暴雨改期', }); expect(cancelRes.success).toBe(true); expect(cancelRes.status).toBe('CANCELLED'); expect(cancelRes.refundedCount).toBe(2); expect(cancelRes.totalRefundCents).toBe(15000); }); it('平台管理员具备全平台活动监管强制解散权限', () => { const act = activityService.createActivity({ creatorId: host.id, title: '涉嫌违规商圈组局', gameCategory: '同城陪喝', payType: ActivityPayType.AA_PAY, aaFeeCents: 5000, maxParticipants: 2, roleManager, }); activityService.joinActivity(act.id, { userId: participant1.id, payAmountCents: 5000 }); // 非管理员调用强制解散被拒绝 expect(() => { activityService.adminForceCancelActivity({ activityId: act.id, operatorAdminId: outsider.id, reason: '违规封禁', roleManager, }); }).toThrow('只有平台管理员有权执行平台强制解散'); // 平台管理员执行强制解散并全额退款 const adminCancelRes = activityService.adminForceCancelActivity({ activityId: act.id, operatorAdminId: admin.id, reason: '违规场所管控下架', roleManager, }); expect(adminCancelRes.success).toBe(true); expect(adminCancelRes.refundedCount).toBe(1); }); }); describe('5. 角色四:平台管理员/运营端 (ADMIN) 仲裁与全流程监管', () => { let buyer: any; let expert: any; let admin: any; let order: any; beforeEach(() => { buyer = roleManager.registerUser({ openid: 'wx_b_arb', nickname: '申诉买家' }); expert = roleManager.registerUser({ openid: 'wx_e_arb', nickname: '涉事大神' }); admin = roleManager.registerAdmin({ openid: 'wx_a_arb', nickname: '总台仲裁法官' }); roleManager.submitExpertCert(expert.id, { realName: '赵选手', idCardNo: '110101199501018888' }); roleManager.approveExpertCert(expert.id, admin.id); order = orderService.createOrder({ buyerId: buyer.id, expertId: expert.id, track: CompanionTrack.ONLINE_GAME, serviceTitle: '王者双排冲国服', hours: 3, totalAmountCents: 10500, }); orderService.payOrder(order.id, buyer.id); orderService.expertAccept(order.id, expert.id, roleManager); orderService.expertApplyFinish(order.id, expert.id); orderService.buyerDispute(order.id, buyer.id, '大神接单后擅自挂机且言语态度恶劣,申请全额退款'); }); it('管理员判定买家胜诉全额退款 (REFUND),订单流转为 REFUNDED', () => { expect(order.status).toBe(OrderStatus.DISPUTED); const arbRes = orderService.arbitrate({ orderId: order.id, operatorAdminId: admin.id, decision: 'REFUND', reason: '查证大神确实严重违规挂机,判定全额退款买家并记录信用扣分', roleManager, }); expect(arbRes.status).toBe(OrderStatus.REFUNDED); expect(arbRes.arbitrationDecision).toBe('REFUND'); expect(arbRes.arbitratedBy).toBe(admin.id); expect(arbRes.arbitratedAt).toBeDefined(); }); it('管理员判定申诉不成立驳回 (RELEASE),订单流转为 COMPLETED', () => { expect(order.status).toBe(OrderStatus.DISPUTED); const arbRes = orderService.arbitrate({ orderId: order.id, operatorAdminId: admin.id, decision: 'RELEASE', reason: '核实对局战绩完整履约且达成胜率指标,驳回申诉并释放服务报酬', roleManager, }); expect(arbRes.status).toBe(OrderStatus.COMPLETED); expect(arbRes.arbitrationDecision).toBe('RELEASE'); expect(arbRes.arbitratedBy).toBe(admin.id); }); it('越权拦截:非平台管理员尝试执行仲裁判定被抛出异常拒绝', () => { const impostor = roleManager.registerUser({ openid: 'wx_impostor', nickname: '假冒管理员' }); expect(() => { orderService.arbitrate({ orderId: order.id, operatorAdminId: impostor.id, decision: 'REFUND', roleManager, }); }).toThrow('只有平台管理员有权执行争议仲裁'); }); it('非 DISPUTED 状态的订单不可执行仲裁判定', () => { const normalOrder = orderService.createOrder({ buyerId: buyer.id, expertId: expert.id, track: CompanionTrack.ONLINE_GAME, serviceTitle: '普通对局', hours: 1, totalAmountCents: 3500, }); expect(() => { orderService.arbitrate({ orderId: normalOrder.id, operatorAdminId: admin.id, decision: 'REFUND', roleManager, }); }).toThrow('非争议仲裁中,不可执行仲裁'); }); }); describe('6. 前端 UniApp 双身份与角色守卫 (Role Guard) 规则门禁验证', () => { const roleJsPath = path.resolve(__dirname, '../../pwuniapp/common/role.js'); const roleJsContent = fs.readFileSync(roleJsPath, 'utf-8'); it('role.js 必须定义四大核心角色 (PLAYER, EXPERT, HOST, ADMIN)', () => { expect(roleJsContent).toContain("PLAYER: 'PLAYER'"); expect(roleJsContent).toContain("EXPERT: 'EXPERT'"); expect(roleJsContent).toContain("HOST: 'HOST'"); expect(roleJsContent).toContain("ADMIN: 'ADMIN'"); }); it('role.js 必须包含完整的认证状态机定义 (CERT_STATUS)', () => { expect(roleJsContent).toContain('NONE: 0'); expect(roleJsContent).toContain('PENDING: 1'); expect(roleJsContent).toContain('APPROVED: 2'); expect(roleJsContent).toContain('REJECTED: 3'); }); it('role.js 必须配置非大神禁止访问的受保护页面清单与 checkRoleGuard 守卫拦截', () => { expect(roleJsContent).toContain("'/pages/expert/workspace'"); expect(roleJsContent).toContain("'/pages/expert/radar'"); expect(roleJsContent).toContain("'/pages/expert/hall'"); expect(roleJsContent).toContain('function checkRoleGuard'); expect(roleJsContent).toContain('function isExpertCertified'); expect(roleJsContent).toContain('function installRoleGuard'); }); it('App.vue 必须在 onLaunch 中注册 installRoleGuard 全局路由守卫', () => { const appVuePath = path.resolve(__dirname, '../../pwuniapp/App.vue'); const appVueContent = fs.readFileSync(appVuePath, 'utf-8'); expect(appVueContent).toContain('installRoleGuard'); expect(appVueContent).toContain('installRoleGuard()'); }); it('CustomTabBar 底部导航栏必须对未认证非大神访问接单端进行权限拦截', () => { const tabbarPath = path.resolve(__dirname, '../../pwuniapp/components/CustomTabBar.vue'); const tabbarContent = fs.readFileSync(tabbarPath, 'utf-8'); expect(tabbarContent).toContain('isExpertCertified'); }); it('三大专家页面必须在 onShow 生命周期中配置兜底权限重定向防穿透', () => { const expertPages = [ path.resolve(__dirname, '../../pwuniapp/pages/expert/workspace.vue'), path.resolve(__dirname, '../../pwuniapp/pages/expert/radar.vue'), path.resolve(__dirname, '../../pwuniapp/pages/expert/hall.vue'), ]; for (const p of expertPages) { const content = fs.readFileSync(p, 'utf-8'); expect(content).toContain('isExpertCertified'); expect(content).toContain('/pages/user/index'); } }); it('大神端冷启动或刷新进入小程序首页时,首页与 App.vue 必须自动路由至大神接单大厅', () => { const indexPath = path.resolve(__dirname, '../../pwuniapp/pages/index/index.vue'); const indexContent = fs.readFileSync(indexPath, 'utf-8'); expect(indexContent).toContain('checkExpertRedirect'); expect(indexContent).toContain('/pages/expert/hall'); expect(indexContent).toContain('ROLE_TYPES.EXPERT'); const appPath = path.resolve(__dirname, '../../pwuniapp/App.vue'); const appContent = fs.readFileSync(appPath, 'utf-8'); expect(appContent).toContain('/pages/expert/hall'); expect(appContent).toContain('ROLE_TYPES.EXPERT'); }); }); });