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'); }); }); 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('后端 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'); }); }); 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('handleRemoveFavorite'); }); }); describe('6. 系统与隐私设置闭环验证', () => { it('个人中心页面必须包含手机号脱敏计算、服务通知与清理缓存能力', () => { const userVuePath = path.join(uniPagesDir, 'user', 'index.vue'); const content = fs.readFileSync(userVuePath, 'utf-8'); expect(content).toContain('openSettingsModal'); expect(content).toContain('privacyHidePhone'); expect(content).toContain('displayPhone'); expect(content).toContain('clearLocalCache'); expect(content).toContain('cacheSizeText'); expect(content).toContain('v2.6.0 正式版'); }); }); 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'); }); }); });