- 移除附近活动页顶部单独的派单按钮(入口重复) - 「我要发单」新增发布类型切换:发起组局 / 平台派单,派单模式填预算与时长并内联拉起支付,支付成功进入待接单池 - 接单大厅新增「附近组局(可报名)」区块,展示招募中的组局并可进入详情报名 - 113 套件/738 用例全绿
58 行
2.5 KiB
TypeScript
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([]);
|
|
});
|
|
});
|