// 已读取AGENTS.md import * as fs from 'fs'; import * as path from 'path'; describe('PC总后台管理端核心业务与闭环能力自动化测试 (Post Manage, Finance CSV Export, WeChat Template Config)', () => { const rootDir = path.resolve(__dirname, '../../'); const serverIndexPath = path.join(rootDir, 'server/src/index.ts'); const serverRepoPath = path.join(rootDir, 'server/src/db/repository.ts'); const serverMysqlPath = path.join(rootDir, 'server/src/db/mysql.ts'); const adminPostManageVuePath = path.join(rootDir, 'admin/arco-design-pro-vite/src/views/community/post-manage/index.vue'); const adminFinanceRecordsVuePath = path.join(rootDir, 'admin/arco-design-pro-vite/src/views/finance/records/index.vue'); const adminFinanceWithdrawVuePath = path.join(rootDir, 'admin/arco-design-pro-vite/src/views/finance/withdraw/index.vue'); const adminPlatformConfigVuePath = path.join(rootDir, 'admin/arco-design-pro-vite/src/views/safety/platform-config/index.vue'); const adminCommunityRouterPath = path.join(rootDir, 'admin/arco-design-pro-vite/src/router/routes/modules/community.ts'); const adminUserListVuePath = path.join(rootDir, 'admin/arco-design-pro-vite/src/views/user/list/index.vue'); describe('1. 友圈广场动态管理与置顶/状态流转/级联删除闭环验证', () => { it('数据库层必须扩展 posts 表结构并包含 is_top 与 status 字段', () => { const mysqlCode = fs.readFileSync(serverMysqlPath, 'utf-8'); expect(mysqlCode).toContain('ALTER TABLE posts ADD COLUMN is_top'); expect(mysqlCode).toContain('ALTER TABLE posts ADD COLUMN status'); }); it('仓储层 MysqlRepository 必须实现动态多维筛选、一键置顶、状态切换与评论管理', () => { const repoCode = fs.readFileSync(serverRepoPath, 'utf-8'); expect(repoCode).toContain('async getPosts('); expect(repoCode).toContain('ORDER BY is_top DESC, created_at DESC'); expect(repoCode).toContain('async togglePostTop('); expect(repoCode).toContain('async updatePostStatus('); expect(repoCode).toContain('async adminDeletePost('); expect(repoCode).toContain('async adminDeletePostComment('); }); it('后端服务 index.ts 必须注册动态管控的核心管理端 API 路由', () => { const serverCode = fs.readFileSync(serverIndexPath, 'utf-8'); expect(serverCode).toContain("app.get('/api/admin/posts'"); expect(serverCode).toContain("app.post('/api/admin/posts/:id/top'"); expect(serverCode).toContain("app.post('/api/admin/posts/:id/status'"); expect(serverCode).toContain("app.delete('/api/admin/posts/:id'"); expect(serverCode).toContain("app.delete('/api/admin/posts/comments/:commentId'"); }); it('动态管理前端视图 post-manage/index.vue 必须具备完整交互与多图预览', () => { const postManageCode = fs.readFileSync(adminPostManageVuePath, 'utf-8'); expect(postManageCode).toContain('handleToggleTop'); expect(postManageCode).toContain('handleToggleStatus'); expect(postManageCode).toContain('handleDeletePost'); expect(postManageCode).toContain('handleDeleteComment'); expect(postManageCode).toContain('a-image-preview-group'); expect(postManageCode).toContain('a-drawer'); expect(postManageCode).toContain('/api/admin/posts'); }); }); describe('2. 财务管理对账明细导出 CSV 规范校验', () => { it('资金交易流水导出必须包含 UTF-8 BOM 与标准字段', () => { const recordsFile = fs.readFileSync(adminFinanceRecordsVuePath, 'utf-8'); expect(recordsFile).toContain('exportCSV'); expect(recordsFile).toContain('\\uFEFF'); expect(recordsFile).toContain('流水号'); expect(recordsFile).toContain('交易类型'); expect(recordsFile).toContain('金额(元)'); expect(recordsFile).toContain('资金方向'); expect(recordsFile).toContain('交易对手方'); expect(recordsFile).toContain('关联业务ID'); expect(recordsFile).toContain('记账时间'); }); it('提现审核列表导出必须包含 UTF-8 BOM 与标准字段', () => { const withdrawFile = fs.readFileSync(adminFinanceWithdrawVuePath, 'utf-8'); expect(withdrawFile).toContain('exportCSV'); expect(withdrawFile).toContain('\\uFEFF'); expect(withdrawFile).toContain('提现单号'); expect(withdrawFile).toContain('申请大神'); expect(withdrawFile).toContain('大神ID'); expect(withdrawFile).toContain('提现金额(元)'); expect(withdrawFile).toContain('账户余额(元)'); expect(withdrawFile).toContain('结算渠道'); expect(withdrawFile).toContain('申请时间'); expect(withdrawFile).toContain('审核状态'); }); }); describe('3. 微信小程序订阅消息与服务通知管理配置及模拟推送', () => { it('平台配置种子与增量迁移中必须包含 message_template_rules', () => { const mysqlCode = fs.readFileSync(serverMysqlPath, 'utf-8'); expect(mysqlCode).toContain('message_template_rules'); expect(mysqlCode).toContain('orderStatusTemplateId'); expect(mysqlCode).toContain('activityJoinTemplateId'); expect(mysqlCode).toContain('withdrawSuccessTemplateId'); expect(mysqlCode).toContain('verifyCodeTemplateId'); }); it('后台平台配置面板已集成微信服务通知与模板消息 Tab 及测试推送控制台', () => { const configViewCode = fs.readFileSync(adminPlatformConfigVuePath, 'utf-8'); expect(configViewCode).toContain('message_template_rules'); expect(configViewCode).toContain('orderStatusTemplateId'); expect(configViewCode).toContain('activityJoinTemplateId'); expect(configViewCode).toContain('withdrawSuccessTemplateId'); expect(configViewCode).toContain('verifyCodeTemplateId'); expect(configViewCode).toContain('sendTestMessage'); expect(configViewCode).toContain('/api/admin/message/test-send'); }); it('后端必须提供模拟测试推送端点并正确返回结构化结果', () => { const serverCode = fs.readFileSync(serverIndexPath, 'utf-8'); expect(serverCode).toContain("app.post('/api/admin/message/test-send'"); expect(serverCode).toContain('wechatMsgId'); expect(serverCode).toContain('status'); expect(serverCode).toContain('sendTime'); }); it('管理后台路由正确注册 community 模块并挂载 post-manage', () => { expect(fs.existsSync(adminCommunityRouterPath)).toBe(true); const routerCode = fs.readFileSync(adminCommunityRouterPath, 'utf-8'); expect(routerCode).toContain('/community'); expect(routerCode).toContain('post-manage'); expect(routerCode).toContain('icon-message'); }); }); describe('4. 全平台用户列表删除与批量删除闭环校验', () => { it('数据仓储层 MysqlRepository 必须实现 deleteUser 与 deleteUsers 方法', () => { const repoCode = fs.readFileSync(serverRepoPath, 'utf-8'); expect(repoCode).toContain('async deleteUser(id: number): Promise'); expect(repoCode).toContain('async deleteUsers(ids: number[]): Promise'); expect(repoCode).toContain('DELETE FROM users WHERE id = ?'); expect(repoCode).toContain('DELETE FROM users WHERE id IN'); }); it('后端服务 index.ts 必须注册单个删除与批量删除 API 路由', () => { const serverCode = fs.readFileSync(serverIndexPath, 'utf-8'); expect(serverCode).toContain("app.delete('/api/users/:id'"); expect(serverCode).toContain("app.post('/api/users/batch-delete'"); expect(serverCode).toContain('repo.deleteUser('); expect(serverCode).toContain('repo.deleteUsers('); }); it('用户列表前端视图 user/list/index.vue 必须包含行级删除与批量删除按钮及确认弹窗', () => { const userListCode = fs.readFileSync(adminUserListVuePath, 'utf-8'); expect(userListCode).toContain('handleDeleteUser'); expect(userListCode).toContain('handleBatchDelete'); expect(userListCode).toContain('selectedRowKeys'); expect(userListCode).toContain('icon-delete'); expect(userListCode).toContain('a-popconfirm'); expect(userListCode).toContain('/api/users/batch-delete'); expect(userListCode).toContain('/api/users/${record.id}'); }); }); describe('5. 大神认证资质解除与接单状态持久化闭环校验', () => { const adminCompanionListVuePath = path.join(rootDir, 'admin/arco-design-pro-vite/src/views/companion/list/index.vue'); const adminCompanionAuditVuePath = path.join(rootDir, 'admin/arco-design-pro-vite/src/views/companion/audit/index.vue'); const uniApiJsPath = path.join(rootDir, 'pwuniapp/common/api.js'); it('仓储层 auditCompanion 必须支持 revoke 解除认证动作并联动回退用户角色与双身份标识', () => { const repoCode = fs.readFileSync(serverRepoPath, 'utf-8'); expect(repoCode).toContain("action: 'approve' | 'reject' | 'revoke'"); expect(repoCode).toContain('已解除大神认证资质'); expect(repoCode).toContain('const isCertExpert = companionRow ? certStatus === 2 : (certStatus === 2 || roleHasExpert)'); }); it('管理后台大神档案库必须支持解除认证弹窗与接单状态真实持久化', () => { const listCode = fs.readFileSync(adminCompanionListVuePath, 'utf-8'); expect(listCode).toContain('解除认证'); expect(listCode).toContain('openRevokeModal'); expect(listCode).toContain('handleRevokeSubmit'); expect(listCode).toContain("action: 'revoke'"); expect(listCode).toContain('axios.put(`/api/companions/${record.id}`'); }); it('管理后台入驻资质审核页必须支持对已通过大神解除认证及对已驳回记录恢复认证', () => { const auditCode = fs.readFileSync(adminCompanionAuditVuePath, 'utf-8'); expect(auditCode).toContain('解除认证'); expect(auditCode).toContain('恢复认证'); expect(auditCode).toContain("openRejectModal(record, 'revoke')"); }); it('小程序端获取同城大神列表必须过滤未通过或已解除认证的记录', () => { const apiCode = fs.readFileSync(uniApiJsPath, 'utf-8'); expect(apiCode).toContain("c.auditStatus === '已通过'"); }); it('微信支付配置必须支持微信支付平台公钥上传与商户证书序列号自动提取', () => { const configViewCode = fs.readFileSync(adminPlatformConfigVuePath, 'utf-8'); const serverCode = fs.readFileSync(serverIndexPath, 'utf-8'); expect(configViewCode).toContain('platformPublicKeyId'); expect(configViewCode).toContain('platformPublicKey'); expect(configViewCode).toContain("triggerPemSelect('platformPublicKey')"); expect(configViewCode).toContain('extractCertSerialNo'); expect(serverCode).toContain("app.post('/api/admin/pay/wechat/parse-cert'"); expect(serverCode).toContain('extractSerialNoFromCertPem'); }); }); describe('6. 全流程无 Emoji 门禁合规校验', () => { it('新增的前后端代码中严禁包含任何系统 Emoji 图标字符', () => { const filesToCheck = [ adminCommunityRouterPath, adminPostManageVuePath, adminFinanceRecordsVuePath, adminFinanceWithdrawVuePath, adminPlatformConfigVuePath, adminUserListVuePath, path.join(rootDir, 'admin/arco-design-pro-vite/src/views/companion/list/index.vue'), path.join(rootDir, 'admin/arco-design-pro-vite/src/views/companion/audit/index.vue') ]; const emojiRegex = /[\uD83C-\uDBFF\uDC00-\uDFFF\u2600-\u26FF\u2700-\u27BF]/; for (const file of filesToCheck) { const content = fs.readFileSync(file, 'utf-8'); expect(emojiRegex.test(content)).toBe(false); } }); }); });