262 行
12 KiB
TypeScript
262 行
12 KiB
TypeScript
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('/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');
|
|
});
|
|
});
|
|
});
|
|
|
|
|