// 已读取AGENTS.md import * as fs from 'fs'; import * as path from 'path'; /** * 第九十八批:后台人工调整余额。 * 背景:测试与运营需要给用户钱包充值/扣减,但后台没有入口,只能改库。 * 要求:必须管理员权限、事务锁行、禁止扣成负数、写财务流水。 */ describe('第九十八批后台余额调整闸门', () => { const root = path.resolve(__dirname, '../../'); const read = (rel: string) => fs.readFileSync(path.join(root, rel), 'utf-8'); const indexCode = read('server/src/index.ts'); const repo = read('server/src/db/repository.ts'); const adminPage = read('admin/arco-design-pro-vite/src/views/user/list/index.vue'); it('接口必须要求管理员权限', () => { expect(indexCode).toContain("app.post('/api/admin/users/:id/balance', requireAdmin"); expect(indexCode).toContain('adjustUserBalance'); }); it('仓储层必须锁行、校验负数并写财务流水', () => { const start = repo.indexOf('async adjustUserBalance('); expect(start).toBeGreaterThan(-1); const block = repo.slice(start, start + 3200); expect(block).toContain('FOR UPDATE'); expect(block).toContain('余额不足'); expect(block).toContain('后台余额充值'); expect(block).toContain('后台余额扣减'); expect(block).toContain('INSERT INTO finance_records'); }); it('后台用户列表必须提供「调余额」入口与弹窗', () => { expect(adminPage).toContain('openBalanceModal'); expect(adminPage).toContain('balanceModalVisible'); expect(adminPage).toContain('/api/admin/users/${balanceTarget.value.id}/balance'); expect(adminPage).toContain('调整用户钱包余额'); }); it('后台判断接口成功必须读取 interceptor 解包后的 res.code,禁止误判 res.data.code', () => { expect(adminPage).toContain('res && res.code === 20000'); const adminRoot = path.resolve(root, 'admin/arco-design-pro-vite/src'); const offenders: string[] = []; const walk = (dir: string) => { for (const entry of fs.readdirSync(dir, { withFileTypes: true })) { const full = path.join(dir, entry.name); if (entry.isDirectory()) walk(full); else if (entry.name.endsWith('.vue') || entry.name.endsWith('.ts')) { const text = fs.readFileSync(full, 'utf-8'); if (text.includes('.data.code === 20000')) offenders.push(path.relative(adminRoot, full)); } } }; walk(adminRoot); expect(offenders).toEqual([]); }); });