import * as fs from 'fs'; import * as path from 'path'; import { ContentSecurityService } from '../src/core/security/content-security.service'; describe('ClientFeatures (TDD 测试套件: 用户端功能闭环)', () => { const rootDir = path.resolve(__dirname, '../../'); const uniPagesDir = path.join(rootDir, 'pwuniapp', 'pages'); const serverSrcDir = path.join(rootDir, 'server', 'src'); const contentSecurityService = new ContentSecurityService(); describe('1. 举报违规行为通道闭环验证', () => { it('后端 repository 必须实现 createReport 与 getReports 并支持存证入库', () => { const repoPath = path.join(serverSrcDir, 'db', 'repository.ts'); const content = fs.readFileSync(repoPath, 'utf-8'); expect(content).toContain('createReport'); expect(content).toContain('getReports'); expect(content).toContain('reports'); expect(content).toContain('reporter_id'); expect(content).toContain('target_type'); }); it('后端 index.ts 必须暴露 POST /api/reports 路由并集成文本安全检测', () => { const indexPath = path.join(serverSrcDir, 'index.ts'); const content = fs.readFileSync(indexPath, 'utf-8'); expect(content).toContain("app.post('/api/reports'"); expect(content).toContain('contentSecurityService.checkText'); expect(content).toContain('repo.createReport'); }); it('违规线索说明命中涉黄/欺诈敏感词时,内容安全风控应拦截', () => { const checkResult = contentSecurityService.checkText('加微信私下转账免手续费直接打款'); expect(checkResult.pass).toBe(false); expect(checkResult.code).toBe(87014); expect(checkResult.suggest).toBe('block'); }); it('后端 repository 必须实现 handleReport 处置流转并支持处分与驳回', () => { const repoPath = path.join(serverSrcDir, 'db', 'repository.ts'); const content = fs.readFileSync(repoPath, 'utf-8'); expect(content).toContain('handleReport'); expect(content).toContain('handle_result'); expect(content).toContain('handle_note'); expect(content).toContain('handled_at'); }); it('后端 index.ts 必须暴露 POST /api/reports/:id/handle 路由', () => { const indexPath = path.join(serverSrcDir, 'index.ts'); const content = fs.readFileSync(indexPath, 'utf-8'); expect(content).toContain("app.post('/api/reports/:id/handle'"); expect(content).toContain('repo.handleReport'); }); it('管理后台必须挂载 SafetyReports 路由且存在对应处理页面视图', () => { const routePath = path.join(rootDir, 'admin', 'arco-design-pro-vite', 'src', 'router', 'routes', 'modules', 'safety.ts'); const routeContent = fs.readFileSync(routePath, 'utf-8'); expect(routeContent).toContain("path: 'reports'"); expect(routeContent).toContain("name: 'SafetyReports'"); const viewPath = path.join(rootDir, 'admin', 'arco-design-pro-vite', 'src', 'views', 'safety', 'reports', 'index.vue'); expect(fs.existsSync(viewPath)).toBe(true); const viewContent = fs.readFileSync(viewPath, 'utf-8'); expect(viewContent).toContain('/api/reports'); expect(viewContent).toContain('handleAction'); }); it('小程序端 Toast 提示必须在 7 汉字以内以防被微信小程序方块图标截断', () => { const orderListPath = path.join(uniPagesDir, 'order', 'list.vue'); const orderContent = fs.readFileSync(orderListPath, 'utf-8'); expect(orderContent).toContain("title: '举报提交成功'"); const detailPath = path.join(uniPagesDir, 'expert', 'detail.vue'); const detailContent = fs.readFileSync(detailPath, 'utf-8'); expect(detailContent).toContain("title: '举报提交成功'"); }); }); describe('2. 订单待支付倒计时与退款仲裁工单闭环验证', () => { it('15分钟待支付倒计时计算逻辑应准确', () => { const createdAt = new Date().toISOString(); const createdTime = new Date(createdAt).getTime(); const expireTime = createdTime + 15 * 60 * 1000; const now = createdTime + 5 * 60 * 1000; // 过去了5分钟 const remainingSeconds = Math.max(0, Math.floor((expireTime - now) / 1000)); expect(remainingSeconds).toBe(10 * 60); // 剩余 600 秒 const minutes = Math.floor(remainingSeconds / 60); const seconds = remainingSeconds % 60; expect(`${String(minutes).padStart(2, '0')}:${String(seconds).padStart(2, '0')}`).toBe('10:00'); }); it('后端 repository 必须实现 applyOrderRefund 并原子写入 arbitrations 仲裁表', () => { const repoPath = path.join(serverSrcDir, 'db', 'repository.ts'); const content = fs.readFileSync(repoPath, 'utf-8'); expect(content).toContain('applyOrderRefund'); expect(content).toContain('争议仲裁中'); expect(content).toContain('arbitrations'); }); it('仲裁全额退款用户时,必须原子将订单金额退还买家钱包余额并记录入账流水', () => { const repoPath = path.join(serverSrcDir, 'db', 'repository.ts'); const content = fs.readFileSync(repoPath, 'utf-8'); expect(content).toContain('UPDATE users SET balance = balance + ?'); expect(content).toContain('仲裁退款'); expect(content).toContain('finance_records'); }); it('提现申请被管理员驳回时,必须原子将预扣佣金原路退还用户佣金账户并记录冲正流水', () => { const repoPath = path.join(serverSrcDir, 'db', 'repository.ts'); const content = fs.readFileSync(repoPath, 'utf-8'); expect(content).toContain('UPDATE users SET commission = commission + ?'); expect(content).toContain('提现驳回退还'); }); it('后端 repository 与管理后台必须支持平台管理员强制违规下架组局并原路退款搭子', () => { const repoPath = path.join(serverSrcDir, 'db', 'repository.ts'); const content = fs.readFileSync(repoPath, 'utf-8'); expect(content).toContain('cancelActivity'); expect(content).toContain('isAdmin'); const adminActListPath = path.join(rootDir, 'admin', 'arco-design-pro-vite', 'src', 'views', 'activity', 'list', 'index.vue'); const adminActContent = fs.readFileSync(adminActListPath, 'utf-8'); expect(adminActContent).toContain('handleCancelActivity'); expect(adminActContent).toContain('违规下架'); }); it('后端 index.ts POST /api/orders/:id/refund 必须接收理由与说明并执行风控拦截', () => { const indexPath = path.join(serverSrcDir, 'index.ts'); const content = fs.readFileSync(indexPath, 'utf-8'); expect(content).toContain("app.post('/api/orders/:id/refund'"); expect(content).toContain('reason'); expect(content).toContain('contentSecurityService.checkText'); expect(content).toContain('repo.applyOrderRefund'); }); }); describe('3. 个人资料性别与签名更新闭环验证', () => { it('后端 repository 必须在 updateUserProfile 中支持 gender 与 signature 字段处理', () => { const repoPath = path.join(serverSrcDir, 'db', 'repository.ts'); const content = fs.readFileSync(repoPath, 'utf-8'); expect(content).toContain('gender'); expect(content).toContain('signature'); expect(content).toContain('slice(0, 64)'); }); it('后端 index.ts POST /api/users/:id/profile 必须支持性别白名单与个性签名风控', () => { const indexPath = path.join(serverSrcDir, 'index.ts'); const content = fs.readFileSync(indexPath, 'utf-8'); expect(content).toContain("app.post('/api/users/:id/profile'"); expect(content).toContain('signature'); expect(content).toContain('contentSecurityService.checkText'); }); it('后端 index.ts POST /api/users/:id/profile 必须校验手机号与微信号格式并拦截非法输入', () => { const indexPath = path.join(serverSrcDir, 'index.ts'); const content = fs.readFileSync(indexPath, 'utf-8'); expect(content).toContain('1[3-9]'); expect(content).toContain('a-zA-Z'); expect(content).toContain('联系方式格式不正确'); }); it('前端 user/index.vue 必须集成联系方式格式校验与非法标点过滤', () => { const userVuePath = path.join(uniPagesDir, 'user', 'index.vue'); const content = fs.readFileSync(userVuePath, 'utf-8'); expect(content).toContain('validateContact'); expect(content).toContain('onPhoneInput'); expect(content).toContain('field-label-row'); }); }); describe('4. 前端核心页面规范与结构完整性校验', () => { it('个人中心页面 (user/index.vue) 必须包含个性签名展示与性别修改入口', () => { const userVuePath = path.join(uniPagesDir, 'user', 'index.vue'); const content = fs.readFileSync(userVuePath, 'utf-8'); expect(content).toContain('user.signature'); expect(content).toContain('editForm.gender'); expect(content).toContain('editForm.signature'); expect(content).toContain('个性签名'); }); it('订单中心页面 (order/list.vue) 必须包含倒计时徽章、退款仲裁工单与举报入口', () => { const orderVuePath = path.join(uniPagesDir, 'order', 'list.vue'); const content = fs.readFileSync(orderVuePath, 'utf-8'); expect(content).toContain('countdown-badge'); expect(content).toContain('getOrderCountdown'); expect(content).toContain('openRefundModal'); expect(content).toContain('openReportModal'); expect(content).toContain('refundForm'); expect(content).toContain('reportForm'); }); it('陪玩详情页面 (expert/detail.vue) 必须包含违规举报操作入口与弹窗', () => { const detailVuePath = path.join(uniPagesDir, 'expert', 'detail.vue'); const content = fs.readFileSync(detailVuePath, 'utf-8'); expect(content).toContain('openReportModal'); expect(content).toContain('submitReportTicket'); expect(content).toContain('presetReportReasons'); expect(content).toContain('review-modal-mask'); }); }); describe('5. 我的收藏陪玩闭环验证', () => { it('后端 repository 必须实现收藏陪玩管理方法 (getFavorites, addFavorite, removeFavorite, isFavorite)', () => { const repoPath = path.join(serverSrcDir, 'db', 'repository.ts'); const content = fs.readFileSync(repoPath, 'utf-8'); expect(content).toContain('getFavorites'); expect(content).toContain('addFavorite'); expect(content).toContain('removeFavorite'); expect(content).toContain('isFavorite'); expect(content).toContain('favorites'); }); it('后端 index.ts 必须暴露收藏相关 API 路由', () => { const indexPath = path.join(serverSrcDir, 'index.ts'); const content = fs.readFileSync(indexPath, 'utf-8'); expect(content).toContain("app.get('/api/favorites'"); expect(content).toContain("app.post('/api/favorites'"); expect(content).toContain("app.delete('/api/favorites/:expertId'"); expect(content).toContain("app.get('/api/favorites/check'"); }); it('陪玩详情页、个人中心与独立收藏页面必须实现收藏状态联动与列表渲染', () => { const detailVuePath = path.join(uniPagesDir, 'expert', 'detail.vue'); const detailContent = fs.readFileSync(detailVuePath, 'utf-8'); expect(detailContent).toContain('toggleFavorite'); expect(detailContent).toContain('isFav'); // 个人中心点击我的收藏直接跳转独立页面 const userVuePath = path.join(uniPagesDir, 'user', 'index.vue'); const userContent = fs.readFileSync(userVuePath, 'utf-8'); expect(userContent).toContain('openFavoritesModal'); expect(userContent).toContain('favoriteList'); expect(userContent).toContain('/pages/user/favorites'); // 独立收藏页面完整闭环与能力 const favVuePath = path.join(uniPagesDir, 'user', 'favorites.vue'); expect(fs.existsSync(favVuePath)).toBe(true); const favContent = fs.readFileSync(favVuePath, 'utf-8'); expect(favContent).toContain('removeFavorite'); expect(favContent).toContain('handleCancelFavorite'); expect(favContent).toContain('filterOnlyOnline'); expect(favContent).toContain('isOnline'); }); }); describe('6. 系统与隐私设置闭环验证', () => { it('个人中心与独立设置页面必须实现手机号脱敏计算、服务通知、清理缓存与版本合规', () => { // 个人中心验证:包含脱敏计算与跳转独立设置页 const userVuePath = path.join(uniPagesDir, 'user', 'index.vue'); const userContent = fs.readFileSync(userVuePath, 'utf-8'); expect(userContent).toContain('openSettingsModal'); expect(userContent).toContain('privacyHidePhone'); expect(userContent).toContain('displayPhone'); expect(userContent).toContain('clearLocalCache'); expect(userContent).toContain('cacheSizeText'); expect(userContent).toContain('/pages/user/settings'); // 独立系统与隐私设置页面规格验证 const settingsVuePath = path.join(uniPagesDir, 'user', 'settings.vue'); expect(fs.existsSync(settingsVuePath)).toBe(true); const settingsContent = fs.readFileSync(settingsVuePath, 'utf-8'); expect(settingsContent).toContain('privacyHidePhone'); expect(settingsContent).toContain('onToggleHidePhone'); expect(settingsContent).toContain('noticeSubscribe'); expect(settingsContent).toContain('onToggleNotice'); expect(settingsContent).toContain('clearLocalCache'); expect(settingsContent).toContain('cacheSizeText'); expect(settingsContent).toContain('v2.6.0 正式版'); expect(settingsContent).toContain('goToProtocol'); }); }); describe('7. 分销推广管理闭环验证', () => { it('后端 repository 必须实现下级团队与返佣订单流水方法', () => { const repoPath = path.join(serverSrcDir, 'db', 'repository.ts'); const content = fs.readFileSync(repoPath, 'utf-8'); expect(content).toContain('getDistributionTeam'); expect(content).toContain('getDistributionRecords'); expect(content).toContain('distribution_records'); }); it('后端 index.ts 必须暴露分销团队与返佣订单路由', () => { const indexPath = path.join(serverSrcDir, 'index.ts'); const content = fs.readFileSync(indexPath, 'utf-8'); expect(content).toContain("app.get('/api/distribution/team'"); expect(content).toContain("app.get('/api/distribution/records'"); }); it('个人中心页面必须实现专属海报、团队列表、返佣订单与佣金提现看板', () => { const userVuePath = path.join(uniPagesDir, 'user', 'index.vue'); const content = fs.readFileSync(userVuePath, 'utf-8'); expect(content).toContain('openDistributionModal'); expect(content).toContain('distActiveTab'); expect(content).toContain('distributionTeam'); expect(content).toContain('distributionRecords'); expect(content).toContain('submitDistWithdraw'); expect(content).toContain('suncode-box'); }); }); describe('8. 首页搜索、多维复合筛选与雷达即时速配闭环验证', () => { it('首页页面 (index/index.vue) 必须包含搜索框、热搜推荐词与多维复合筛选工具栏', () => { const indexVuePath = path.join(uniPagesDir, 'index', 'index.vue'); const content = fs.readFileSync(indexVuePath, 'utf-8'); expect(content).toContain('searchKeyword'); expect(content).toContain('hotSearchWords'); expect(content).toContain('filterOnlyOnline'); expect(content).toContain('filterGender'); expect(content).toContain('filterSort'); expect(content).toContain('filterPriceRange'); expect(content).toContain('priceOptions'); }); it('首页页面必须实现雷达即时派单速配与搜索无结果重置能力', () => { const indexVuePath = path.join(uniPagesDir, 'index', 'index.vue'); const content = fs.readFileSync(indexVuePath, 'utf-8'); expect(content).toContain('startInstantMatch'); expect(content).toContain('matchedExpert'); expect(content).toContain('radar-scan-box'); expect(content).toContain('empty-search-box'); expect(content).toContain('resetAllFilters'); }); it('即时匹配必须实时拉取大神、只匹配真实注册且接单中的大神,并支持换一位', () => { const indexVuePath = path.join(uniPagesDir, 'index', 'index.vue'); const content = fs.readFileSync(indexVuePath, 'utf-8'); const apiContent = fs.readFileSync(path.join(rootDir, 'pwuniapp', 'common', 'api.js'), 'utf-8'); expect(content).toContain('matchCompanions({ ...(pos || {}), radius: 5, limit: 20 })'); expect(content).toContain('lastMatchedId'); expect(content).toContain('matchedMeta'); expect(content).toContain('换一位'); expect(content).toContain('在线大神即时匹配'); expect(content).not.toContain('首位接单大神命中'); expect(apiContent).toContain('userId: Number(c.userId) || 0'); expect(apiContent).toContain('export async function matchCompanions'); }); }); describe('9. 大神认证审核双向联动、服务评价监管与友圈搭子上车业务完整闭环', () => { it('审核大神入驻通过时,必须联动更新 users 表 role 为 "认证大神" 与 is_expert = 1', () => { const repoPath = path.join(serverSrcDir, 'db', 'repository.ts'); const content = fs.readFileSync(repoPath, 'utf-8'); expect(content).toContain('auditCompanion'); expect(content).toContain('UPDATE users SET role = "认证大神", is_expert = 1'); expect(content).toContain('UPDATE users SET role = "普通用户", is_expert = 0'); }); it('后端必须提供评价与回复监管接口 (GET /api/reviews 与 DELETE /api/reviews/:id)', () => { const indexPath = path.join(serverSrcDir, 'index.ts'); const content = fs.readFileSync(indexPath, 'utf-8'); expect(content).toContain("app.get('/api/reviews'"); expect(content).toContain("app.delete('/api/reviews/:id'"); }); it('管理后台路由与菜单中必须挂载服务评价与回复监管模块', () => { const routePath = path.join(rootDir, 'admin', 'arco-design-pro-vite', 'src', 'router', 'routes', 'modules', 'community.ts'); const content = fs.readFileSync(routePath, 'utf-8'); expect(content).toContain('review-manage'); expect(content).toContain('menu.community.reviewManage'); }); it('后端必须提供搭子上车申请查询与审核处置接口', () => { const indexPath = path.join(serverSrcDir, 'index.ts'); const content = fs.readFileSync(indexPath, 'utf-8'); expect(content).toContain("app.get('/api/posts/:id/applies'"); expect(content).toContain("app.get('/api/post-applies'"); expect(content).toContain("app.post('/api/post-applies/:id/handle'"); }); it('管理后台动态管理必须支持查看搭子上车申请并执行同意/婉拒处置', () => { const postManagePath = path.join(rootDir, 'admin', 'arco-design-pro-vite', 'src', 'views', 'community', 'post-manage', 'index.vue'); const content = fs.readFileSync(postManagePath, 'utf-8'); expect(content).toContain('openAppliesDrawer'); expect(content).toContain('appliesDrawerVisible'); expect(content).toContain('handleApplyAction'); expect(content).toContain('/api/post-applies/'); }); }); describe('10. 全链路跨端业务闭环深度校验 (实名审核回流/完单结算+二级返佣/争议结案/搭子审批/举报反馈/大神ID双向解析)', () => { it('大神认证申请与个人中心必须实时同步后端 certStatus 与 rejectReason,支持驳回后查看原因并重新提审', () => { const applyVuePath = path.join(uniPagesDir, 'expert', 'apply.vue'); const applyContent = fs.readFileSync(applyVuePath, 'utf-8'); expect(applyContent).toContain('syncRemoteCertStatus'); expect(applyContent).toContain('rejectReason'); const userIndexPath = path.join(uniPagesDir, 'user', 'index.vue'); const userContent = fs.readFileSync(userIndexPath, 'utf-8'); expect(userContent).toContain('setExpertCertStatus'); expect(userContent).toContain('rejectReason'); }); it('订单确认完成或评价时,后端必须触发 completeOrderWithSettlement 自动结算大神佣金与一二级分销返佣', () => { const repoPath = path.join(serverSrcDir, 'db', 'repository.ts'); const content = fs.readFileSync(repoPath, 'utf-8'); expect(content).toContain('completeOrderWithSettlement'); expect(content).toContain('大神完单收益结算'); expect(content).toContain('INSERT INTO distribution_records'); }); it('争议仲裁驳回退款或扣信用分时,必须同步将关联订单从争议仲裁中流转为已完成并触发结算', () => { const repoPath = path.join(serverSrcDir, 'db', 'repository.ts'); const content = fs.readFileSync(repoPath, 'utf-8'); expect(content).toContain('handleArbitration'); expect(content).toContain("SET status = '已完成'"); expect(content).toContain('this.completeOrderWithSettlement(shouldSettleOrderId)'); }); it('友圈动态必须返回搭子上车申请状态 myApplyStatus,且小程序端支持房主直接审批申请与申请人查看状态', () => { const communityVuePath = path.join(uniPagesDir, 'community', 'index.vue'); const content = fs.readFileSync(communityVuePath, 'utf-8'); expect(content).toContain('handleManageApplies'); expect(content).toContain('getPostApplies'); expect(content).toContain('handlePostApply'); expect(content).toContain('myApplyStatus'); }); it('用户提交举报投诉后,个人中心必须提供“我的举报与维权反馈”查询入口,且管理后台工作台联动待办提醒', () => { const userIndexPath = path.join(uniPagesDir, 'user', 'index.vue'); const userContent = fs.readFileSync(userIndexPath, 'utf-8'); expect(userContent).toContain('openMyReportsModal'); expect(userContent).toContain('getMyReports'); expect(userContent).toContain('我的举报与维权反馈'); const todoListPath = path.join(rootDir, 'admin', 'arco-design-pro-vite', 'src', 'views', 'dashboard', 'workplace', 'components', 'quick-todo-list.vue'); const todoContent = fs.readFileSync(todoListPath, 'utf-8'); expect(todoContent).toContain('pendingReports'); expect(todoContent).toContain('/safety/reports'); }); it('分销推广中心必须支持绑定上级邀请码 (POST /api/distribution/bind) 并实时汇总下级贡献佣金', () => { const indexPath = path.join(serverSrcDir, 'index.ts'); const indexContent = fs.readFileSync(indexPath, 'utf-8'); expect(indexContent).toContain("app.post('/api/distribution/bind'"); const promoteVuePath = path.join(uniPagesDir, 'user', 'promote.vue'); const promoteContent = fs.readFileSync(promoteVuePath, 'utf-8'); expect(promoteContent).toContain('handleBindInviteCode'); expect(promoteContent).toContain('bindInviteCode'); }); it('大神 ID 必须支持 1..6 与 101..106 双向解析 (resolveCompanionRow),确保收藏列表、评价、资料修改与在线状态切换全链路一致', () => { const repoPath = path.join(serverSrcDir, 'db', 'repository.ts'); const content = fs.readFileSync(repoPath, 'utf-8'); expect(content).toContain('resolveCompanionRow'); const hallVuePath = path.join(uniPagesDir, 'expert', 'hall.vue'); const hallContent = fs.readFileSync(hallVuePath, 'utf-8'); expect(hallContent).toContain('updateCompanionProfile'); const workspaceVuePath = path.join(uniPagesDir, 'expert', 'workspace.vue'); const wsContent = fs.readFileSync(workspaceVuePath, 'utf-8'); expect(wsContent).toContain('updateCompanionProfile'); expect(wsContent).toContain('completeOrderApi'); }); it('3.1 活动 4 位核销码必须执行 CAS 原子防重核销,并在核销后触发发起人费用清算与付费活动二级分润', () => { const repoPath = path.join(serverSrcDir, 'db', 'repository.ts'); const content = fs.readFileSync(repoPath, 'utf-8'); expect(content).toContain('WHERE id = ? AND checkin_status = 0'); expect(content).toContain('settleActivityCheckinAndReferral'); expect(content).toContain('活动核销费用清算'); }); it('3.2 双身份架构必须维护 role_flags (1:玩家, 2:大神, 3:双身份) 并在抢单接口拦截未通过实名认证的大神档案', () => { const mysqlPath = path.join(serverSrcDir, 'db', 'mysql.ts'); const mysqlContent = fs.readFileSync(mysqlPath, 'utf-8'); expect(mysqlContent).toContain('role_flags'); const repoPath = path.join(serverSrcDir, 'db', 'repository.ts'); const repoContent = fs.readFileSync(repoPath, 'utf-8'); expect(repoContent).toContain('roleFlags'); expect(repoContent).toContain('大神档案未通过实名认证审核,无法接单'); }); it('3.3 分销裂变必须建立 distribution_relations 父子终身关联表,并支持小程序扫码/带参进入自动锁粉绑定', () => { const mysqlPath = path.join(serverSrcDir, 'db', 'mysql.ts'); const mysqlContent = fs.readFileSync(mysqlPath, 'utf-8'); expect(mysqlContent).toContain('CREATE TABLE IF NOT EXISTS distribution_relations'); const repoPath = path.join(serverSrcDir, 'db', 'repository.ts'); const repoContent = fs.readFileSync(repoPath, 'utf-8'); expect(repoContent).toContain('INSERT INTO distribution_relations'); expect(repoContent).toContain('当前账号已终身绑定上级推荐人,不可重复修改绑定'); const indexVuePath = path.join(uniPagesDir, 'index', 'index.vue'); const indexContent = fs.readFileSync(indexVuePath, 'utf-8'); expect(indexContent).toContain('PENDING_INVITER_CODE'); }); it('3.4 已封禁用户核心业务防线:后端仓储与路由必须全面拦截发单、报名、下单与动态评论', () => { const serverIndexPath = path.join(serverSrcDir, 'index.ts'); const indexContent = fs.readFileSync(serverIndexPath, 'utf-8'); expect(indexContent).toContain('assertUserNotBanned'); expect(indexContent).toContain('您的账号已被管理员封禁'); const repoPath = path.join(serverSrcDir, 'db', 'repository.ts'); const repoContent = fs.readFileSync(repoPath, 'utf-8'); expect(repoContent).toContain("(u.status IS NULL OR u.status != '已封禁')"); expect(repoContent).toContain("UPDATE activities SET status = 'CANCELLED' WHERE creator_id = ? AND status = 'RECRUITING'"); const rolePath = path.join(rootDir, 'pwuniapp', 'common', 'role.js'); const roleContent = fs.readFileSync(rolePath, 'utf-8'); expect(roleContent).toContain('isUserBanned'); expect(roleContent).toContain("uInfo.status === '已封禁'"); }); }); });