文件
shanghaipeiwan/server/tests/secure-mode-gates.spec.ts
xcbyy ecc4a116c4 feat(pay): 商家转账场景 1005 打通提现打款
- 转账场景报备信息按佣金报酬要求固定传「岗位类型/报酬说明」两段,否则微信参数校验拒绝

- 处理 WAIT_USER_CONFIRM(用户确认模式):推送「提现待你确认收款」,提示 24 小时内确认

- 提现申请增加单笔上限校验(finance_rules.maxWithdrawAmount,默认 200,对齐微信商家转账额度)

- /api/posts 身份优先取服务端会话 token

- 文档补充佣金报酬场景与用户确认模式说明(109 套件/722 用例全绿)
2026-10-21 14:03:22 +08:00

77 行
3.8 KiB
TypeScript

// 已读取AGENTS.md
import * as fs from 'fs';
import * as path from 'path';
/**
* 第九十三批:安全闸门与 NODE_ENV 解耦 + 支付/提现 fail-closed。
* 背景:线上以 NODE_ENV=development 运行,导致所有 `IS_PRODUCTION && ...` 闸门失效:
* - 后台可用默认口令 admin/admin123 登录;
* - 提现审核通过只改状态、不真实打款;
* - 4 位核销码可被任意登录用户枚举/核销;
* - 匿名可上传、可无限调用内容安全检测。
* 另外小程序在预支付失败时会伪造"沙箱支付成功",必须改为抛错。
*/
describe('第九十三批安全闸门解耦与支付 fail-closed', () => {
const root = path.resolve(__dirname, '../../');
const indexCode = fs.readFileSync(path.join(root, 'server/src/index.ts'), 'utf-8');
const apiCode = fs.readFileSync(path.join(root, 'pwuniapp/common/api.js'), 'utf-8');
const routeBlock = (marker: string, len = 3000) => {
const start = indexCode.indexOf(marker);
expect(start).toBeGreaterThan(-1);
return indexCode.slice(start, start + len);
};
it('安全闸门统一由 SECURE_MODE 控制,不依赖 NODE_ENV', () => {
expect(indexCode).toContain('const SECURE_MODE = !ALLOW_INSECURE_DEV_AUTH;');
expect(routeBlock("app.post('/api/security/msg-sec-check'")).toContain('if (SECURE_MODE)');
expect(indexCode).not.toContain('if (IS_PRODUCTION && !ADMIN_PASSWORD_CONFIGURED)');
expect(indexCode).not.toContain('if (IS_PRODUCTION) {\n try {\n resolveSecureUserId');
});
it('提现审核未开通真实转账时必须 503,禁止只改状态不打款', () => {
const block = routeBlock("app.post('/api/finance/withdrawals/:id/review'", 6000);
expect(block).not.toContain('IS_PRODUCTION && !ALLOW_SANDBOX_PAY');
expect(block).toContain('if (!ALLOW_SANDBOX_PAY)');
expect(block).toContain('微信商家转账未就绪');
});
it('预支付在未开启沙箱时拒绝下发沙箱参数', () => {
const block = routeBlock("app.post('/api/pay/wechat/prepay'", 12000);
const sandboxIdx = block.indexOf("mode: 'SANDBOX'");
expect(sandboxIdx).toBeGreaterThan(-1);
expect(block.slice(0, sandboxIdx)).toContain('if (!ALLOW_SANDBOX_PAY)');
});
it('订阅消息版本支持 MINIPROGRAM_STATE 显式配置', () => {
expect(indexCode).toContain('process.env.MINIPROGRAM_STATE');
expect(indexCode).toContain('const MINIPROGRAM_STATE = (');
expect(indexCode).not.toContain("miniprogram_state: IS_PRODUCTION ? 'formal' : 'developer'");
});
it('小程序预支付失败必须抛错,不得伪造沙箱支付成功', () => {
expect(apiCode).not.toContain('SANDBOX_FALLBACK');
expect(apiCode).toContain('微信支付发起失败,请稍后重试');
expect(apiCode).toMatch(/catch \(err\) \{\n \/\/ 预支付失败必须向上抛出/);
});
it('动态列表与点赞的身份统一走 normalizeUserId', () => {
expect(apiCode).toContain('const uid = normalizeUserId(userId);\n let query = `?userId=${uid}`;');
expect(apiCode).toContain('const uid = normalizeUserId(userId);\n const payload = { userId: uid };');
});
it('商家转账:待用户确认时必须提示收款人确认,并约束单笔额度', () => {
const review = routeBlock("app.post('/api/finance/withdrawals/:id/review'", 6000);
expect(review).toContain("state === 'WAIT_USER_CONFIRM'");
expect(review).toContain('提现待你确认收款');
const withdraw = routeBlock("app.post('/api/users/:id/withdraw'", 2500);
expect(withdraw).toContain('maxWithdrawAmount');
expect(withdraw).toContain('微信商家转账单笔限额');
});
it('动态列表身份优先取服务端会话', () => {
const posts = routeBlock("app.get('/api/posts'", 1200);
expect(posts).toContain('resolveRequestUserId(req)');
});
});