文件
shanghaipeiwan/server/tests/admin-balance-adjust.spec.ts
xcbyy 33b6a3ea7d feat(activity): 发单页统一组局与平台派单入口,接单大厅展示附近组局
- 移除附近活动页顶部单独的派单按钮(入口重复)

- 「我要发单」新增发布类型切换:发起组局 / 平台派单,派单模式填预算与时长并内联拉起支付,支付成功进入待接单池

- 接单大厅新增「附近组局(可报名)」区块,展示招募中的组局并可进入详情报名

- 113 套件/738 用例全绿
2026-10-21 18:03:09 +08:00

58 行
2.5 KiB
TypeScript

// 已读取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([]);
});
});