feat(security): 三端闭环加固 + 微信提现打款/订阅消息落地

后端: 身份与归属鉴权收口; 资金安全(结算闸门/支付防篡改/报名费真实扣款/赠金/结算唯一入账/提现打款); 数据泄露与隐私; 登录与密钥安全(默认密钥闸门/登录限流); 可观测性; 启动自检.

微信: 商家转账到零钱提现打款闭环(含回调/查单/失败退回佣金); 订阅消息真实下发闭环与后台模板字段对齐.

后台: 财务/微信支付配置读取失败锁定; 看板/待办/走势去假数据; 打款与测试推送文案修正.

小程序: 请求层错误透传与登录过期提示; 多页失败反馈与乐观回滚; 帮助中心FAQ页面化.

测试: 新增 40+ 回归护栏与真实 MySQL 冒烟(75 套件/599 用例全绿).
这个提交包含在:
xcbyy
2026-10-17 18:00:09 +08:00
父节点 78117bb4e1
当前提交 2599dce315
共有 70 个文件被更改,包括 3806 次插入 和 330 次删除

查看文件

@@ -0,0 +1,31 @@
// 已读取AGENTS.md
import * as fs from 'fs';
import * as path from 'path';
/**
* 第二十六批回归闸门:组局创建参数服务端校验
*/
describe('第二十六批组局创建校验闸门', () => {
const rootDir = path.resolve(__dirname, '../../');
const repoCode = fs.readFileSync(path.join(rootDir, 'server/src/db/repository.ts'), 'utf-8');
const block = () => {
const start = repoCode.indexOf('async createActivity(');
expect(start).toBeGreaterThan(-1);
return repoCode.slice(start, start + 1600);
};
it('付费类型/人数/费用必须服务端归一化', () => {
const b = block();
expect(b).toContain('const payType = Number(data.payType) === 2 ? 2 : 1;');
expect(b).toContain('Math.min(100, Math.max(1, Math.round(rawMax)))');
expect(b).toContain('MAX_FEE_CENTS');
expect(b).toContain('const perPersonFeeCents = payType === 1 ? fee : 0;');
});
it('不得再直接信任客户端未校验费用', () => {
const b = block();
expect(b).not.toContain('const perPersonFeeCents = data.perPersonFeeCents || 5000;');
expect(b).not.toContain('const maxParticipants = data.maxParticipants || 4;');
});
});

查看文件

@@ -0,0 +1,32 @@
// 已读取AGENTS.md
import * as fs from 'fs';
import * as path from 'path';
/**
* 第二十八批回归闸门:组局详情不得泄露他人核销码
*/
describe('第二十八批组局详情核销码隐私闸门', () => {
const rootDir = path.resolve(__dirname, '../../');
const indexCode = fs.readFileSync(path.join(rootDir, 'server/src/index.ts'), 'utf-8');
const apiCode = fs.readFileSync(path.join(rootDir, 'pwuniapp/common/api.js'), 'utf-8');
it('活动详情路由必须剥离非本人核销码', () => {
const start = indexCode.indexOf("app.get('/api/activities/:id',");
expect(start).toBeGreaterThan(-1);
const block = indexCode.slice(start, start + 1200);
expect(block).toContain('严禁向他人泄露');
expect(block).toContain('Number(p.userId) === callerId');
expect(block).toContain('const { verifyCode, ...rest } = p;');
expect(block).toContain('isVerifiedAdminRequest(req)');
});
it('小程序请求详情携带当前用户身份', () => {
const start = apiCode.indexOf('export async function getActivityDetail(');
const block = apiCode.slice(start, start + 300);
expect(block).toContain('/activities/${id}?userId=${normalizeUserId()}');
});
it('数据适配器不得为缺失核销码伪造 8392', () => {
expect(apiCode).not.toContain("verifyCode: p.verifyCode || '8392'");
});
});

查看文件

@@ -0,0 +1,71 @@
// 已读取AGENTS.md
import * as fs from 'fs';
import * as path from 'path';
/**
* 第十一批回归闸门:组局发起/解散、抢单、搭子上车、用户设置的身份收口
*/
describe('第十一批写接口身份收口闸门 (组局 / 抢单 / 上车 / 设置)', () => {
const rootDir = path.resolve(__dirname, '../../');
const indexCode = fs.readFileSync(path.join(rootDir, 'server/src/index.ts'), 'utf-8');
const apiCode = fs.readFileSync(path.join(rootDir, 'pwuniapp/common/api.js'), 'utf-8');
const routeBlock = (marker: string, len = 1200) => {
const start = indexCode.indexOf(marker);
expect(start).toBeGreaterThan(-1);
return indexCode.slice(start, start + len);
};
describe('1. 组局发起人身份以服务端会话为准', () => {
it('创建组局必须经 resolveSecureUserId 绑定发起人', () => {
const block = routeBlock("app.post('/api/activities',");
expect(block).toContain("'发起组局'");
expect(block).not.toContain("req.body?.creatorId || req.headers['x-user-id'] || 1001");
});
});
describe('2. 组局解散人身份以服务端会话为准', () => {
it('解散组局:管理员走签名 token,其余经 resolveSecureUserId', () => {
const block = routeBlock("app.post('/api/activities/:id/cancel'");
expect(block).toContain('isVerifiedAdminRequest(req)');
expect(block).toContain("'解散组局'");
expect(block).not.toContain("const callerId = Number(hostId ||");
});
});
describe('3. 抢单身份以服务端会话为准', () => {
it('接单必须经 resolveSecureUserId 且写库用服务端身份', () => {
const block = routeBlock("app.post('/api/orders/:id/claim'");
expect(block).toContain("'接单'");
expect(block).toContain('claimOrder(orderId, { ...(req.body || {}), expertId: operatorId })');
});
});
describe('4. 搭子上车申请身份以服务端会话为准', () => {
it('上车申请必须经 resolveSecureUserId', () => {
const block = routeBlock("app.post('/api/posts/:id/apply'");
expect(block).toContain("'申请加入搭子车队'");
expect(block).not.toContain("applicantId || req.headers['x-user-id'] || 1001");
});
});
describe('5. 用户设置读写归属', () => {
it('设置读写必须经 resolveSecureUserId', () => {
const getBlock = routeBlock("app.get('/api/users/:id/settings'");
const postBlock = routeBlock("app.post('/api/users/:id/settings'");
expect(getBlock).toContain("'查看个人设置'");
expect(postBlock).toContain("'保存个人设置'");
});
});
describe('6. 小程序端写接口携带真实身份', () => {
it('组局/抢单/上车/报名/退席均注入当前用户身份', () => {
expect(apiCode).toContain('creatorId: normalizeUserId(payload.creatorId)');
expect(apiCode).toContain('hostId: normalizeUserId(hostId)');
expect(apiCode).toContain('userId: normalizeUserId(params.userId)');
expect(apiCode).toContain('userId: normalizeUserId(userId),');
expect(apiCode).toContain('userId: normalizeUserId(expertData.userId)');
expect(apiCode).toContain('applicantId: normalizeUserId(applyData.applicantId)');
});
});
});

查看文件

@@ -0,0 +1,37 @@
// 已读取AGENTS.md
import * as fs from 'fs';
import * as path from 'path';
/**
* 第二十四批回归闸门:组局报名费用服务端权威 + 真实扣款
*/
describe('第二十四批组局报名资金闸门', () => {
const rootDir = path.resolve(__dirname, '../../');
const repoCode = fs.readFileSync(path.join(rootDir, 'server/src/db/repository.ts'), 'utf-8');
const joinBlock = () => {
const start = repoCode.indexOf('async joinActivity(');
expect(start).toBeGreaterThan(-1);
return repoCode.slice(start, start + 6200);
};
it('报名费以服务端活动配置为准(AA=人均分摊,房主请客=0)', () => {
const block = joinBlock();
expect(block).toContain('per_person_fee_cents');
expect(block).toContain('const joinFeeCents = Number(actRows[0].pay_type) === 1');
});
it('报名从钱包真实扣款', () => {
const block = joinBlock();
expect(block).toContain("'活动报名扣款'");
expect(block).toContain('UPDATE users SET balance = balance - ?');
expect(block).toContain('钱包余额不足,报名需支付');
});
it('入账/参与者金额均使用服务端权威值', () => {
const block = joinBlock();
expect(block).not.toContain('params.payAmountCents / 100');
expect(block).not.toContain('params.userId, params.payAmountCents, verifyCode');
expect(block).toContain('payAmountCents: joinFeeCents');
});
});

查看文件

@@ -0,0 +1,32 @@
// 已读取AGENTS.md
import * as fs from 'fs';
import * as path from 'path';
/**
* 第二十五批回归闸门:房主改价的金额校验与差额记账
*/
describe('第二十五批改价完整性闸门', () => {
const rootDir = path.resolve(__dirname, '../../');
const repoCode = fs.readFileSync(path.join(rootDir, 'server/src/db/repository.ts'), 'utf-8');
const block = () => {
const start = repoCode.indexOf('async adjustPrice(');
expect(start).toBeGreaterThan(-1);
return repoCode.slice(start, start + 1800);
};
it('必须校验调价金额(非负整数 + 平台上限)', () => {
const b = block();
expect(b).toContain('Number.isInteger(price)');
expect(b).toContain('MAX_FEE_CENTS');
expect(b).toContain('调价金额必须为不小于 0 的整数');
});
it('必须按真实差额记账而非整份新价', () => {
const b = block();
expect(b).toContain('const deltaCents = price - oldFee');
expect(b).toContain("'房主追加赏金'");
expect(b).toContain("'房主下调出价差额'");
expect(b).not.toContain("'房主追加赏金差额退补'");
});
});

查看文件

@@ -0,0 +1,33 @@
// 已读取AGENTS.md
import * as fs from 'fs';
import * as path from 'path';
/**
* 第五十六批回归闸门:提现打款场景信息可在后台自助配置
*/
describe('第五十六批打款场景信息配置闸门', () => {
const rootDir = path.resolve(__dirname, '../../');
const indexCode = fs.readFileSync(path.join(rootDir, 'server/src/index.ts'), 'utf-8');
const adminConfig = fs.readFileSync(
path.join(rootDir, 'admin/arco-design-pro-vite/src/views/finance/config/index.vue'),
'utf-8'
);
it('后台提供转账场景ID/备注/姓名加密/回调/场景信息配置项', () => {
expect(adminConfig).toContain('transferSceneId');
expect(adminConfig).toContain('transferRemark');
expect(adminConfig).toContain('transferUserNameEnabled');
expect(adminConfig).toContain('transferNotifyUrl');
expect(adminConfig).toContain('transferSceneReportInfosText');
expect(adminConfig).toContain('transfer_scene_report_infos');
});
it('服务端持久化并透传场景信息', () => {
// 保存接口白名单
expect(indexCode).toContain('transferSceneReportInfos: Array.isArray(incoming.transferSceneReportInfos)');
// 运行时配置解析透传
const start = indexCode.indexOf('async function resolveWechatPayRuntimeConfig(');
const block = indexCode.slice(start, start + 2600);
expect(block).toContain('transferSceneReportInfos');
});
});

查看文件

@@ -0,0 +1,29 @@
// 已读取AGENTS.md
import * as fs from 'fs';
import * as path from 'path';
/**
* 第十七批回归闸门:管理端写接口鉴权(用户删除 / 场所管理 / 订单超时扫描)
*/
describe('第十七批管理端写接口鉴权闸门', () => {
const rootDir = path.resolve(__dirname, '../../');
const indexCode = fs.readFileSync(path.join(rootDir, 'server/src/index.ts'), 'utf-8');
it('删除用户仅管理员可执行', () => {
expect(indexCode).toContain("app.delete('/api/users/:id', requireAdmin");
});
it('场所增删改仅管理员可执行', () => {
expect(indexCode).toContain("app.post('/api/safety/places', requireAdmin");
expect(indexCode).toContain("app.put('/api/safety/places/:id', requireAdmin");
expect(indexCode).toContain("app.delete('/api/safety/places/:id', requireAdmin");
});
it('场所列表仍保持公开可读(小程序合规场所库)', () => {
expect(indexCode).toContain("app.get('/api/safety/places', async");
});
it('订单超时自动流转扫描仅管理员可触发', () => {
expect(indexCode).toContain("app.post('/api/orders/process-timeouts', requireAdmin");
});
});

查看文件

@@ -0,0 +1,28 @@
// 已读取AGENTS.md
import * as fs from 'fs';
import * as path from 'path';
/**
* 第十八批回归闸门:生产环境不得使用仓库内置默认 admin 密钥
*/
describe('第十八批鉴权密钥安全闸门', () => {
const rootDir = path.resolve(__dirname, '../../');
const indexCode = fs.readFileSync(path.join(rootDir, 'server/src/index.ts'), 'utf-8');
it('不得再以仓库内置默认密钥作为静默回落', () => {
expect(indexCode).not.toContain("process.env.ADMIN_TOKEN_SECRET || 'peiwan_admin_token_secret_v1'");
});
it('必须存在默认密钥常量与生产环境随机化闸门', () => {
expect(indexCode).toContain('const DEFAULT_ADMIN_TOKEN_SECRET =');
const start = indexCode.indexOf('const DEFAULT_ADMIN_TOKEN_SECRET =');
const block = indexCode.slice(start, start + 1200);
expect(block).toContain("process.env.NODE_ENV === 'production'");
expect(block).toContain('crypto.randomBytes');
expect(block).toContain('SECURITY');
});
it('管理员 token 校验必须使用常量时间比较', () => {
expect(indexCode).toContain('crypto.timingSafeEqual');
});
});

查看文件

@@ -0,0 +1,28 @@
// 已读取AGENTS.md
import * as fs from 'fs';
import * as path from 'path';
/**
* 第三十六批回归闸门:买家取消订单的状态安全
*/
describe('第三十六批买家取消订单闸门', () => {
const rootDir = path.resolve(__dirname, '../../');
const indexCode = fs.readFileSync(path.join(rootDir, 'server/src/index.ts'), 'utf-8');
const repoCode = fs.readFileSync(path.join(rootDir, 'server/src/db/repository.ts'), 'utf-8');
it('仓储层提供 buyerCancelOrder 且拒绝已履约订单', () => {
const start = repoCode.indexOf('async buyerCancelOrder(');
expect(start).toBeGreaterThan(-1);
const block = repoCode.slice(start, start + 2600);
expect(block).toContain('不可直接取消,请发起退款申诉');
expect(block).toContain("status = \"已退款\", refund_amount");
expect(block).toContain("'买家取消订单退款'");
});
it('取消路由:管理员直改状态,买家走安全取消', () => {
const start = indexCode.indexOf("app.post('/api/orders/:id/cancel'");
const block = indexCode.slice(start, start + 1200);
expect(block).toContain('role.isAdmin');
expect(block).toContain('repo.buyerCancelOrder(orderId)');
});
});

查看文件

@@ -0,0 +1,30 @@
// 已读取AGENTS.md
import * as fs from 'fs';
import * as path from 'path';
/**
* 第三十三批回归闸门:code2session 不泄露 session_key 且 openid 绑定需会话一致
*/
describe('第三十三批 code2session 加固闸门', () => {
const rootDir = path.resolve(__dirname, '../../');
const indexCode = fs.readFileSync(path.join(rootDir, 'server/src/index.ts'), 'utf-8');
const block = () => {
const start = indexCode.indexOf("app.post('/api/wechat/code2session'");
expect(start).toBeGreaterThan(-1);
return indexCode.slice(start, start + 2600);
};
it('不得向客户端返回 session_key', () => {
const b = block();
expect(b).not.toContain('sessionKey: session_key');
expect(b).not.toContain('const { openid, session_key, unionid }');
});
it('openid 绑定必须经服务端会话身份', () => {
const b = block();
expect(b).toContain("'绑定微信账户'");
expect(b).toContain('repo.updateUserOpenid(claimed, openid)');
expect(b).not.toContain('repo.updateUserOpenid(Number(userId), openid)');
});
});

查看文件

@@ -0,0 +1,28 @@
// 已读取AGENTS.md
import * as fs from 'fs';
import * as path from 'path';
/**
* 第四十八批回归闸门:大神入驻申请不得同名抢占他人档案
*/
describe('第四十八批大神入驻归属闸门', () => {
const rootDir = path.resolve(__dirname, '../../');
const repoCode = fs.readFileSync(path.join(rootDir, 'server/src/db/repository.ts'), 'utf-8');
const block = () => {
const start = repoCode.indexOf('async applyCompanion(');
expect(start).toBeGreaterThan(-1);
return repoCode.slice(start, start + 3000);
};
it('重新提交仅按 user_id 匹配本人档案', () => {
const b = block();
expect(b).toContain('WHERE user_id IS NOT NULL AND user_id = ?');
expect(b).not.toContain('OR name = ?`, [userId, name]');
expect(b).not.toContain('SET user_id = ?, name = ?');
});
it('昵称与他人档案冲突时拒绝提交', () => {
expect(block()).toContain('该大神昵称已被占用,请更换昵称后重新提交');
});
});

查看文件

@@ -0,0 +1,19 @@
// 已读取AGENTS.md
import * as fs from 'fs';
import * as path from 'path';
/**
* 第三十一批回归闸门:大神详情公开端剥离内部审核备注
*/
describe('第三十一批大神详情隐私闸门', () => {
const rootDir = path.resolve(__dirname, '../../');
const indexCode = fs.readFileSync(path.join(rootDir, 'server/src/index.ts'), 'utf-8');
it('公开详情剥离 rejectReason,管理端保留完整', () => {
const start = indexCode.indexOf("app.get('/api/companions/:id',");
expect(start).toBeGreaterThan(-1);
const block = indexCode.slice(start, start + 800);
expect(block).toContain('isVerifiedAdminRequest(req)');
expect(block).toContain('const { rejectReason, ...publicCompanion } = companion;');
});
});

查看文件

@@ -0,0 +1,20 @@
// 已读取AGENTS.md
import * as fs from 'fs';
import * as path from 'path';
/**
* 第三十批回归闸门:大神公开目录仅展示已通过并脱敏内部审核备注
*/
describe('第三十批大神目录隐私闸门', () => {
const rootDir = path.resolve(__dirname, '../../');
const indexCode = fs.readFileSync(path.join(rootDir, 'server/src/index.ts'), 'utf-8');
it('公开目录仅返回已通过大神且剥离 rejectReason,管理端保留全量', () => {
const start = indexCode.indexOf("app.get('/api/companions',");
expect(start).toBeGreaterThan(-1);
const block = indexCode.slice(start, start + 900);
expect(block).toContain('isVerifiedAdminRequest(req)');
expect(block).toContain("c.auditStatus === '已通过'");
expect(block).toContain('const { rejectReason, ...rest } = c;');
});
});

查看文件

@@ -0,0 +1,21 @@
// 已读取AGENTS.md
import * as fs from 'fs';
import * as path from 'path';
/**
* 第四十六批回归闸门:内容安全检测接口鉴权与 openid 可信
*/
describe('第四十六批内容安全接口闸门', () => {
const rootDir = path.resolve(__dirname, '../../');
const indexCode = fs.readFileSync(path.join(rootDir, 'server/src/index.ts'), 'utf-8');
it('生产需登录且 openid 以会话用户为准', () => {
const start = indexCode.indexOf("app.post('/api/security/msg-sec-check'");
expect(start).toBeGreaterThan(-1);
const block = indexCode.slice(start, start + 1200);
expect(block).toContain('IS_PRODUCTION');
expect(block).toContain("'内容安全检测'");
expect(block).toContain('await resolveUserOpenid(callerId)');
expect(block).not.toContain('const { content, openid, scene } = req.body');
});
});

查看文件

@@ -130,7 +130,7 @@ describe('ContentSecurityService & Compliance Architecture (TDD 微信内容安
const raw = fs.readFileSync(pagesJsonPath, 'utf-8');
const config = JSON.parse(raw);
expect(Array.isArray(config.pages)).toBe(true);
expect(config.pages.length).toBe(25);
expect(config.pages.length).toBe(26);
for (const pageItem of config.pages) {
const vueFilePath = path.join(rootDir, 'pwuniapp', `${pageItem.path}.vue`);

查看文件

@@ -0,0 +1,24 @@
// 已读取AGENTS.md
import * as fs from 'fs';
import * as path from 'path';
/**
* 第五十三批回归闸门:分销团队/返佣明细不得用硬编码假数据
*/
describe('第五十三批分销数据真实性闸门', () => {
const rootDir = path.resolve(__dirname, '../../');
const repoCode = fs.readFileSync(path.join(rootDir, 'server/src/db/repository.ts'), 'utf-8');
it('不得再返回硬编码团队成员/返佣记录', () => {
expect(repoCode).not.toContain("'晨曦微风'");
expect(repoCode).not.toContain("'峡谷追风少年'");
expect(repoCode).not.toContain("'dst_101'");
expect(repoCode).not.toContain("'星空漫步者'");
});
it('无真实数据时返回空', () => {
const start = repoCode.indexOf('async getDistributionRecords(');
const block = repoCode.slice(start, start + 900);
expect(block).toContain('return [];');
});
});

查看文件

@@ -0,0 +1,23 @@
// 已读取AGENTS.md
import * as fs from 'fs';
import * as path from 'path';
/**
* 第五十批回归闸门:大神收益统计不得跨大神串算
*/
describe('第五十批大神收益统计范围闸门', () => {
const rootDir = path.resolve(__dirname, '../../');
const repoCode = fs.readFileSync(path.join(rootDir, 'server/src/db/repository.ts'), 'utf-8');
it('不得再用 companion_name LIKE 匹配聚合作弊式占位名', () => {
expect(repoCode).not.toContain("'%我%'");
expect(repoCode).not.toContain('OR companion_name LIKE ?');
});
it('收益汇总与走势只按 expert_id 统计', () => {
const stats = repoCode.slice(repoCode.indexOf('async getExpertStats('), repoCode.indexOf('async getExpertStats(') + 600);
expect(stats).toContain('WHERE expert_id = ?');
const trends = repoCode.slice(repoCode.indexOf('async getExpertRevenueTrends('));
expect(trends.slice(0, 4000)).toContain('WHERE expert_id = ?');
});
});

查看文件

@@ -0,0 +1,24 @@
// 已读取AGENTS.md
import * as fs from 'fs';
import * as path from 'path';
/**
* 第五十八批回归闸门:帮助中心 FAQ 配置一致性
*/
describe('第五十八批帮助中心FAQ闸门', () => {
const rootDir = path.resolve(__dirname, '../../');
const mysqlCode = fs.readFileSync(path.join(rootDir, 'server/src/db/mysql.ts'), 'utf-8');
const userIndex = fs.readFileSync(path.join(rootDir, 'pwuniapp/pages/user/index.vue'), 'utf-8');
it('faq_list 为空时自动回填默认种子', () => {
expect(mysqlCode).toContain('自动回填默认种子');
expect(mysqlCode).toContain("UPDATE platform_configs SET value_json = ? WHERE config_key = 'faq_list'");
expect(mysqlCode).toContain('!Array.isArray(parsed) || parsed.length === 0');
});
it('小程序不再用写死的假 FAQ 兜底文案(改为独立页面展示)', () => {
expect(userIndex).not.toContain('什么是4位核销码?\\n到达活动现场');
const faqPage = fs.readFileSync(path.join(rootDir, 'pwuniapp/pages/user/help-faq.vue'), 'utf-8');
expect(faqPage).toContain('暂无常见问题');
});
});

查看文件

@@ -0,0 +1,43 @@
// 已读取AGENTS.md
import * as fs from 'fs';
import * as path from 'path';
/**
* 第十六批回归闸门:资金写接口(充值 / 订单支付)身份与归属收口
*/
describe('第十六批资金接口身份闸门 (充值 / 订单支付)', () => {
const rootDir = path.resolve(__dirname, '../../');
const indexCode = fs.readFileSync(path.join(rootDir, 'server/src/index.ts'), 'utf-8');
const apiCode = fs.readFileSync(path.join(rootDir, 'pwuniapp/common/api.js'), 'utf-8');
const routeBlock = (marker: string, len = 1400) => {
const start = indexCode.indexOf(marker);
expect(start).toBeGreaterThan(-1);
return indexCode.slice(start, start + len);
};
describe('1. 钱包充值不得为他人刷余额', () => {
it('充值身份以服务端会话为准', () => {
const block = routeBlock("app.post('/api/users/:id/recharge'");
expect(block).toContain("'充值余额'");
expect(block).not.toContain('const userId = Number(req.params.id);\n await assertUserNotBanned(userId, \'充值余额\')');
});
});
describe('2. 订单支付仅限下单买家本人', () => {
it('支付前必须做订单归属校验', () => {
const block = routeBlock("app.post('/api/orders/:id/pay'");
expect(block).toContain('repo.getOrderOwnership(orderId)');
expect(block).toContain("'确认订单支付'");
expect(block).toContain('仅下单买家本人可确认支付该订单');
});
});
describe('3. 小程序支付携带当前用户身份', () => {
it('payOrder 必须注入 userId', () => {
const start = apiCode.indexOf('export async function payOrder(');
const block = apiCode.slice(start, start + 300);
expect(block).toContain('userId: normalizeUserId(payload.userId)');
});
});
});

查看文件

@@ -0,0 +1,32 @@
// 已读取AGENTS.md
import * as fs from 'fs';
import * as path from 'path';
/**
* 第五十九批回归闸门:帮助中心 FAQ 改为独立页面(非弹窗)
*/
describe('第五十九批帮助中心页面化闸门', () => {
const rootDir = path.resolve(__dirname, '../../');
const faqPagePath = path.join(rootDir, 'pwuniapp/pages/user/help-faq.vue');
const pagesJson = fs.readFileSync(path.join(rootDir, 'pwuniapp/pages.json'), 'utf-8');
const userIndex = fs.readFileSync(path.join(rootDir, 'pwuniapp/pages/user/index.vue'), 'utf-8');
it('新增独立 FAQ 页面并注册路由', () => {
expect(fs.existsSync(faqPagePath)).toBe(true);
const page = fs.readFileSync(faqPagePath, 'utf-8');
expect(page).toContain('getPlatformConfigs');
expect(page).toContain('faq_list');
expect(page).toContain('activeIndex');
expect(pagesJson).toContain('"path": "pages/user/help-faq"');
});
it('个人中心点击进入新页面而非弹窗', () => {
const start = userIndex.indexOf('const openFaq');
expect(start).toBeGreaterThan(-1);
const end = userIndex.indexOf('};', start);
const block = userIndex.slice(start, end + 2);
expect(block).toContain("url: '/pages/user/help-faq'");
expect(block).not.toContain('showActionSheet');
expect(block).not.toContain('showModal');
});
});

查看文件

@@ -83,14 +83,14 @@ describe('第四批安全加固闸门 (用户会话身份 / 订单支付 / 上
it('非 LIVE 沙箱入账必须受 ALLOW_SANDBOX_PAY 生产闸门约束', () => {
const start = indexCode.indexOf("app.post('/api/orders/:id/pay'");
expect(start).toBeGreaterThan(-1);
const block = indexCode.slice(start, start + 1400);
const block = indexCode.slice(start, start + 3400);
expect(block).toContain('if (!ALLOW_SANDBOX_PAY)');
expect(block).toContain('禁止直接变更订单支付状态');
});
it('沙箱仅允许推进到合法已支付状态,严禁客户端任意指定终态', () => {
const start = indexCode.indexOf("app.post('/api/orders/:id/pay'");
const block = indexCode.slice(start, start + 1400);
const block = indexCode.slice(start, start + 3400);
expect(block).toContain("const allowedSandboxStatuses = ['履约中', '待接单']");
expect(block).toContain('allowedSandboxStatuses.includes(requested)');
});

查看文件

@@ -0,0 +1,31 @@
// 已读取AGENTS.md
import * as fs from 'fs';
import * as path from 'path';
/**
* 第二十二批回归闸门:管理端登录暴力破解防护 + 常量时间口令比较
*/
describe('第二十二批登录安全闸门', () => {
const rootDir = path.resolve(__dirname, '../../');
const indexCode = fs.readFileSync(path.join(rootDir, 'server/src/index.ts'), 'utf-8');
it('提供常量时间比较与失败计数限流', () => {
expect(indexCode).toContain('function safeStringEqual(');
expect(indexCode).toContain('timingSafeEqual');
expect(indexCode).toContain('loginFailureMap');
expect(indexCode).toContain('LOGIN_MAX_FAILURES');
expect(indexCode).toContain('isLoginLocked');
});
it('登录路由:超限锁定 + 常量时间比较', () => {
const start = indexCode.indexOf("app.post('/api/user/login'");
expect(start).toBeGreaterThan(-1);
const block = indexCode.slice(start, start + 1200);
expect(block).toContain('isLoginLocked(loginKey)');
expect(block).toContain('登录失败次数过多');
expect(block).toContain('recordLoginFailure(loginKey)');
expect(block).toContain('safeStringEqual(String(password), p)');
expect(block).not.toContain('ADMIN_PASSWORDS.includes(String(password))');
expect(block).not.toContain('username === ADMIN_USERNAME');
});
});

查看文件

@@ -0,0 +1,26 @@
// 已读取AGENTS.md
import * as fs from 'fs';
import * as path from 'path';
/**
* 第四十三批回归闸门:大神接单页失败不得伪装成功
*/
describe('第四十三批大神接单反馈闸门', () => {
const rootDir = path.resolve(__dirname, '../../');
const radar = fs.readFileSync(path.join(rootDir, 'pwuniapp/pages/expert/radar.vue'), 'utf-8');
const hall = fs.readFileSync(path.join(rootDir, 'pwuniapp/pages/expert/hall.vue'), 'utf-8');
it('radar 抢单:双重失败必须提示', () => {
const start = radar.indexOf('const handleTakeOrder =');
const block = radar.slice(start, start + 1600);
expect(block).toContain('抢单失败,请稍后重试');
expect(block).toContain('return;');
});
it('hall 接单:失败必须提示', () => {
const start = hall.indexOf('const handleGrabOrder =');
const block = hall.slice(start, start + 1400);
expect(block).toContain('接单失败,请稍后重试');
expect(block).toContain('return;');
});
});

查看文件

@@ -0,0 +1,24 @@
// 已读取AGENTS.md
import * as fs from 'fs';
import * as path from 'path';
/**
* 第四十二批回归闸门:邀请码不得静默丢失 + 举报人身份以当前用户为准
*/
describe('第四十二批邀请与举报身份闸门', () => {
const rootDir = path.resolve(__dirname, '../../');
const userIndex = fs.readFileSync(path.join(rootDir, 'pwuniapp/pages/user/index.vue'), 'utf-8');
const expertDetail = fs.readFileSync(path.join(rootDir, 'pwuniapp/pages/expert/detail.vue'), 'utf-8');
const orderList = fs.readFileSync(path.join(rootDir, 'pwuniapp/pages/order/list.vue'), 'utf-8');
it('邀请码绑定失败保留待绑定码以便重试', () => {
expect(userIndex).toContain('将于下次登录重试');
const start = userIndex.indexOf('PENDING_INVITER_CODE');
expect(start).toBeGreaterThan(-1);
});
it('举报不再硬编码 reporterId=1001(由当前用户身份承担)', () => {
expect(expertDetail).not.toContain('reporterId: 1001');
expect(orderList).not.toContain('reporterId: 1001');
});
});

查看文件

@@ -0,0 +1,79 @@
// 已读取AGENTS.md
import * as fs from 'fs';
import * as path from 'path';
/**
* 第三十八批回归闸门:小程序写操作失败不得再伪装成功
*/
describe('第三十八批小程序失败反馈闸门', () => {
const rootDir = path.resolve(__dirname, '../../');
const workspace = fs.readFileSync(path.join(rootDir, 'pwuniapp/pages/expert/workspace.vue'), 'utf-8');
const orderList = fs.readFileSync(path.join(rootDir, 'pwuniapp/pages/order/list.vue'), 'utf-8');
it('大神工作台:抢单/拒单/提交完成/接单状态切换失败必须提示', () => {
expect(workspace).toContain('抢单失败,请稍后重试');
expect(workspace).toContain('拒单失败,请稍后重试');
expect(workspace).toContain('提交失败,请稍后重试');
expect(workspace).toContain('接单状态切换失败,请稍后重试');
});
it('抢单失败不再无条件提示成功并移出订单池', () => {
const start = workspace.indexOf('const grabOrder = async');
const block = workspace.slice(start, start + 700);
expect(block).toContain('return;');
expect(block.indexOf('抢单失败')).toBeLessThan(block.indexOf("'抢单成功已接单'"));
});
it('订单列表:取消失败必须提示且不本地改成已取消', () => {
const start = orderList.indexOf('const cancelOrder =');
const block = orderList.slice(start, start + 900);
expect(block).toContain('取消失败,请稍后重试');
expect(block).toContain('loadLiveOrders()');
});
it('社区页:删除/上车申请失败必须提示', () => {
const ci = fs.readFileSync(path.join(rootDir, 'pwuniapp/pages/community/index.vue'), 'utf-8');
const cd = fs.readFileSync(path.join(rootDir, 'pwuniapp/pages/community/detail.vue'), 'utf-8');
for (const code of [ci, cd]) {
expect(code).toContain('删除失败,请重试');
expect(code).toContain('申请失败,请重试');
}
});
it('设置保存:API 层不得吞错,页面失败需回滚并提示', () => {
const apiCode = fs.readFileSync(path.join(rootDir, 'pwuniapp/common/api.js'), 'utf-8');
const settings = fs.readFileSync(path.join(rootDir, 'pwuniapp/pages/user/settings.vue'), 'utf-8');
const start = apiCode.indexOf('export async function updateUserSettings(');
const block = apiCode.slice(start, start + 700);
expect(block).toContain('throw err;');
expect(block).not.toContain('return {\n userId: uid,');
expect(settings).toContain('设置同步失败,请稍后重试');
expect(settings).toContain('privacyHidePhone.value = prev');
});
it('点赞:接口层不得吞错,页面需回滚乐观状态', () => {
const apiCode = fs.readFileSync(path.join(rootDir, 'pwuniapp/common/api.js'), 'utf-8');
const likeStart = apiCode.indexOf('export async function likePost(');
const likeBlock = apiCode.slice(likeStart, likeStart + 500);
expect(likeBlock).toContain('throw err;');
expect(likeBlock).not.toContain('return null;');
const ci = fs.readFileSync(path.join(rootDir, 'pwuniapp/pages/community/index.vue'), 'utf-8');
const cd = fs.readFileSync(path.join(rootDir, 'pwuniapp/pages/community/detail.vue'), 'utf-8');
expect(ci).toContain('回滚乐观更新');
expect(cd).toContain('回滚乐观更新');
});
it('订单动作:取消/验收接口层不得吞错,验收失败需提示', () => {
const apiCode = fs.readFileSync(path.join(rootDir, 'pwuniapp/common/api.js'), 'utf-8');
for (const fn of ['export async function cancelOrder(', 'export async function completeOrder(']) {
const block = apiCode.slice(apiCode.indexOf(fn), apiCode.indexOf(fn) + 500);
expect(block).toContain('throw err;');
expect(block).not.toContain('return null;');
}
const orderList2 = fs.readFileSync(path.join(rootDir, 'pwuniapp/pages/order/list.vue'), 'utf-8');
const start = orderList2.indexOf('const confirmComplete =');
const block = orderList2.slice(start, start + 900);
expect(block).toContain('确认验收失败,请稍后重试');
});
});

查看文件

@@ -0,0 +1,36 @@
// 已读取AGENTS.md
import * as fs from 'fs';
import * as path from 'path';
/**
* 第二十一批回归闸门:订单金额与优惠券服务端权威化
*/
describe('第二十一批订单金额完整性闸门', () => {
const rootDir = path.resolve(__dirname, '../../');
const repoCode = fs.readFileSync(path.join(rootDir, 'server/src/db/repository.ts'), 'utf-8');
const createOrderBlock = () => {
const start = repoCode.indexOf('async createOrder(');
expect(start).toBeGreaterThan(-1);
return repoCode.slice(start, start + 4200);
};
it('服务单价必须按服务端大神档案校验', () => {
const block = createOrderBlock();
expect(block).toContain('resolveCompanionRow(expertId)');
expect(block).toContain('compRate');
expect(block).toContain('hourlyRate = compRate');
});
it('订单金额由服务端计算,不再直接信任客户端 amount', () => {
const block = createOrderBlock();
expect(block).toContain('const amount = Number(Math.max(baseAmount - couponDiscount, 0.01).toFixed(2))');
expect(block).not.toContain('const amount = Number(data.amount) || hourlyRate * durationHours;');
});
it('优惠券折扣必须经服务端有效券校验', () => {
expect(repoCode).toContain('async resolveCouponDiscount(');
const block = createOrderBlock();
expect(block).toContain('resolveCouponDiscount(rawDiscount, track, baseAmount)');
});
});

查看文件

@@ -27,7 +27,7 @@ describe('第五批安全加固闸门 (订单越权结算 / 订单身份收口 /
const start = repoCode.indexOf('async completeOrderWithSettlement(');
expect(start).toBeGreaterThan(-1);
const block = repoCode.slice(start, start + 1600);
expect(block).toContain("const settleBlocked = ['待支付', '待接单', '已取消', '已退款', '已全额退款']");
expect(block).toContain("const settleBlocked = ['待支付', '待付款', '待接单', '已取消', '已退款', '已全额退款']");
expect(block).toContain('settleBlocked.includes(String(order.status))');
expect(block).toContain('不可结算');
expect(block).toContain('e.status = 409');

查看文件

@@ -0,0 +1,27 @@
// 已读取AGENTS.md
import * as fs from 'fs';
import * as path from 'path';
/**
* 第三十七批回归闸门:拒单/申请完成的状态闸门
*/
describe('第三十七批订单流转状态闸门', () => {
const rootDir = path.resolve(__dirname, '../../');
const repoCode = fs.readFileSync(path.join(rootDir, 'server/src/db/repository.ts'), 'utf-8');
it('仅「待接单」可拒单', () => {
const start = repoCode.indexOf('async rejectOrder(');
expect(start).toBeGreaterThan(-1);
const block = repoCode.slice(start, start + 900);
expect(block).toContain("!== '待接单'");
expect(block).toContain('仅待接单可拒单');
});
it('申请完成仅限已履约订单', () => {
const start = repoCode.indexOf('async applyCompleteOrder(');
expect(start).toBeGreaterThan(-1);
const block = repoCode.slice(start, start + 1400);
expect(block).toContain("const allowed = ['履约中', '服务中', '已到场核销', '待确认'];");
expect(block).toContain('不可申请完成');
});
});

查看文件

@@ -0,0 +1,20 @@
// 已读取AGENTS.md
import * as fs from 'fs';
import * as path from 'path';
/**
* 第二十九批回归闸门:抢单池只返回可认领的有效订单
*/
describe('第二十九批抢单池过滤闸门', () => {
const rootDir = path.resolve(__dirname, '../../');
const repoCode = fs.readFileSync(path.join(rootDir, 'server/src/db/repository.ts'), 'utf-8');
it('抢单池必须排除终态/已核销订单并过滤已认领订单', () => {
const start = repoCode.indexOf('async getOrdersPool(');
expect(start).toBeGreaterThan(-1);
const block = repoCode.slice(start, start + 1400);
expect(block).toContain("WHERE status NOT IN ('已完成', '已取消'");
expect(block).toContain('claimed_by IS NULL');
expect(block).toContain('LIMIT 20');
});
});

查看文件

@@ -0,0 +1,35 @@
// 已读取AGENTS.md
import * as fs from 'fs';
import * as path from 'path';
/**
* 第十九批回归闸门:预支付金额不可篡改(低价支付高价订单)
*/
describe('第十九批支付金额完整性闸门', () => {
const rootDir = path.resolve(__dirname, '../../');
const indexCode = fs.readFileSync(path.join(rootDir, 'server/src/index.ts'), 'utf-8');
const repoCode = fs.readFileSync(path.join(rootDir, 'server/src/db/repository.ts'), 'utf-8');
it('订单归属解析必须携带订单金额', () => {
const start = repoCode.indexOf('async getOrderOwnership(');
const block = repoCode.slice(start, start + 900);
expect(block).toContain('o.amount');
expect(block).toContain('amount: Number(r.amount)');
});
it('入账必须校验实收金额与支付意图一致', () => {
const start = repoCode.indexOf('async applyPaymentSuccess(');
const block = repoCode.slice(start, start + 1800);
expect(block).toContain('amountMismatch');
expect(block).toContain("reason: 'amount_mismatch'");
});
it('预支付:仅买家可发起,且订单金额以服务端订单为准', () => {
const start = indexCode.indexOf("app.post('/api/pay/wechat/prepay'");
const block = indexCode.slice(start, start + 2200);
expect(block).toContain("'发起微信支付'");
expect(block).toContain('repo.getOrderOwnership(String(orderId))');
expect(block).toContain('payAmount = Number(own.amount) || 0');
expect(block).not.toContain('const uid = Number(userId) || 0;');
});
});

查看文件

@@ -0,0 +1,27 @@
// 已读取AGENTS.md
import * as fs from 'fs';
import * as path from 'path';
/**
* 第三十五批回归闸门:手机号登录身份匹配与联系方式唯一性
*/
describe('第三十五批手机号登录身份闸门', () => {
const rootDir = path.resolve(__dirname, '../../');
const repoCode = fs.readFileSync(path.join(rootDir, 'server/src/db/repository.ts'), 'utf-8');
it('登录优先按微信 openid 精确匹配', () => {
const start = repoCode.indexOf('async findOrCreateUserByPhone(');
expect(start).toBeGreaterThan(-1);
const block = repoCode.slice(start, start + 1600);
expect(block).toContain('WHERE wechat_openid = ? LIMIT 1');
// openid 查询应出现在手机号查询之前
expect(block.indexOf('wechat_openid = ?')).toBeLessThan(block.indexOf('WHERE phone = ?'));
});
it('修改联系方式必须做唯一性校验', () => {
const start = repoCode.indexOf('async updateUserProfile(');
const block = repoCode.slice(start, start + 1500);
expect(block).toContain('该联系方式已被其他账号使用');
expect(block).toContain('WHERE phone = ? AND id <> ?');
});
});

查看文件

@@ -0,0 +1,65 @@
// 已读取AGENTS.md
import * as fs from 'fs';
import * as path from 'path';
/**
* 第十四批回归闸门:举报/评价/上车申请/收藏/大神收益等读接口越权与泄露收口
*/
describe('第十四批读接口越权收口闸门 (举报 / 评价 / 上车申请 / 收藏 / 收益)', () => {
const rootDir = path.resolve(__dirname, '../../');
const indexCode = fs.readFileSync(path.join(rootDir, 'server/src/index.ts'), 'utf-8');
const apiCode = fs.readFileSync(path.join(rootDir, 'pwuniapp/common/api.js'), 'utf-8');
const routeBlock = (marker: string, len = 1200) => {
const start = indexCode.indexOf(marker);
expect(start).toBeGreaterThan(-1);
return indexCode.slice(start, start + len);
};
describe('1. 举报工单列表', () => {
it('管理端全量 / 普通用户仅本人提交', () => {
const block = routeBlock("app.get('/api/reports',");
expect(block).toContain('isVerifiedAdminRequest(req)');
expect(block).toContain("'查看我的举报'");
});
});
describe('2. 全量评价列表仅管理员', () => {
it('GET /api/reviews 必须挂 requireAdmin', () => {
expect(indexCode).toContain("app.get('/api/reviews', requireAdmin");
expect(indexCode).toContain("app.get('/api/post-applies', requireAdmin");
});
});
describe('3. 动态上车申请名单归属', () => {
it('仅发布者本人或管理员可查看某动态的上车申请', () => {
const block = routeBlock("app.get('/api/posts/:id/applies'");
expect(block).toContain("'查看上车申请'");
expect(block).toContain('仅动态发布者本人可查看该动态的上车申请');
});
});
describe('4. 收藏列表/状态归属', () => {
it('必须经 resolveSecureUserId', () => {
expect(routeBlock("app.get('/api/favorites',")).toContain("'查看收藏列表'");
expect(routeBlock("app.get('/api/favorites/check'")).toContain("'查询收藏状态'");
});
});
describe('5. 大神收益数据归属', () => {
it('收益汇总与走势必须经 assertCompanionActor', () => {
const stats = routeBlock("app.get('/api/expert/stats/:id',");
const trends = routeBlock("app.get('/api/expert/stats/:id/trends'");
expect(stats).toContain('assertCompanionActor(req, own');
expect(trends).toContain('assertCompanionActor(req, own');
});
});
describe('6. 小程序端携带当前用户身份', () => {
it('getPostApplies 必须带当前用户 id', () => {
const start = apiCode.indexOf('export async function getPostApplies(');
const block = apiCode.slice(start, start + 320);
expect(block).toContain('/posts/${postId}/applies?userId=${normalizeUserId()}');
});
});
});

查看文件

@@ -0,0 +1,57 @@
// 已读取AGENTS.md
import * as fs from 'fs';
import * as path from 'path';
/**
* 第十二批回归闸门:读接口数据越权/泄露收口(订单全量、我的组局、分销团队)
*/
describe('第十二批读接口数据隔离闸门 (订单 / 我的组局 / 分销)', () => {
const rootDir = path.resolve(__dirname, '../../');
const indexCode = fs.readFileSync(path.join(rootDir, 'server/src/index.ts'), 'utf-8');
const repoCode = fs.readFileSync(path.join(rootDir, 'server/src/db/repository.ts'), 'utf-8');
const apiCode = fs.readFileSync(path.join(rootDir, 'pwuniapp/common/api.js'), 'utf-8');
const routeBlock = (marker: string, len = 1000) => {
const start = indexCode.indexOf(marker);
expect(start).toBeGreaterThan(-1);
return indexCode.slice(start, start + len);
};
describe('1. 订单列表不得向任意客户端下发全量订单', () => {
it('仓储层提供按用户隔离的 getOrdersForUser', () => {
expect(repoCode).toContain('async getOrdersForUser(');
const start = repoCode.indexOf('async getOrdersForUser(');
const block = repoCode.slice(start, start + 1400);
expect(block).toContain('buyer_id = ?');
expect(block).toContain('claimed_by = ?');
expect(block).toContain('companion_name IN (SELECT name FROM companions');
});
it('订单列表:管理端全量,普通用户仅本人相关', () => {
const block = routeBlock("app.get('/api/orders',");
expect(block).toContain('isVerifiedAdminRequest(req)');
expect(block).toContain('repo.getOrdersForUser(uid)');
expect(block).toContain("'查看订单列表'");
});
});
describe('2. 我的组局 / 分销数据归属', () => {
it('my-activities 必须经 resolveSecureUserId', () => {
const block = routeBlock("app.get('/api/users/:id/my-activities'");
expect(block).toContain("'查看我的组局'");
});
it('分销团队与返佣明细必须经 resolveSecureUserId', () => {
expect(routeBlock("app.get('/api/distribution/team'")).toContain("'查看分销团队'");
expect(routeBlock("app.get('/api/distribution/records'")).toContain("'查看返佣明细'");
});
});
describe('3. 小程序端订单列表携带当前用户身份', () => {
it('getOrders 必须带上当前用户 id', () => {
const start = apiCode.indexOf('export async function getOrders(');
const block = apiCode.slice(start, start + 300);
expect(block).toContain('/orders?userId=${normalizeUserId()}');
});
});
});

查看文件

@@ -0,0 +1,35 @@
// 已读取AGENTS.md
import * as fs from 'fs';
import * as path from 'path';
/**
* 第二十批回归闸门:充值赠金必须服务端按阶梯权威计算
*/
describe('第二十批充值赠金权威性闸门', () => {
const rootDir = path.resolve(__dirname, '../../');
const indexCode = fs.readFileSync(path.join(rootDir, 'server/src/index.ts'), 'utf-8');
const repoCode = fs.readFileSync(path.join(rootDir, 'server/src/db/repository.ts'), 'utf-8');
it('仓储层提供服务端赠金计算', () => {
expect(repoCode).toContain('async computeRechargeBonusCents(');
const start = repoCode.indexOf('async computeRechargeBonusCents(');
const block = repoCode.slice(start, start + 700);
expect(block).toContain('FROM recharge_tiers');
expect(block).toContain('status = 1');
expect(block).toContain('ORDER BY amount DESC');
});
it('预支付不再信任客户端赠金', () => {
expect(indexCode).not.toContain('bonusCents: Math.round(Number(bonusAmount || 0) * 100)');
const start = indexCode.indexOf("app.post('/api/pay/wechat/prepay'");
const block = indexCode.slice(start, start + 5200);
expect(block).toContain('computeRechargeBonusCents(amountCents)');
});
it('沙箱直接充值也不再信任客户端赠金', () => {
expect(indexCode).not.toContain('rechargeUserBalance(userId, Number(amount), Number(bonusAmount || 0))');
const start = indexCode.indexOf("app.post('/api/users/:id/recharge'");
const block = indexCode.slice(start, start + 2400);
expect(block).toContain('computeRechargeBonusCents(Math.round(amountYuan * 100))');
});
});

查看文件

@@ -0,0 +1,39 @@
// 已读取AGENTS.md
import * as fs from 'fs';
import * as path from 'path';
/**
* 第二十七批回归闸门:请求层错误透传 + 核销码反查冲突优先级
*/
describe('第二十七批请求层与核销码反查闸门', () => {
const rootDir = path.resolve(__dirname, '../../');
const requestJs = fs.readFileSync(path.join(rootDir, 'pwuniapp/common/request.js'), 'utf-8');
const repoCode = fs.readFileSync(path.join(rootDir, 'server/src/db/repository.ts'), 'utf-8');
describe('1. 小程序请求层', () => {
it('错误必须附带业务 code / statusCode', () => {
expect(requestJs).toContain('bizErr.code = res.data.code');
expect(requestJs).toContain('bizErr.statusCode = res.statusCode');
expect(requestJs).toContain('httpErr.statusCode = res.statusCode');
});
it('登录过期必须清理会话并提示', () => {
expect(requestJs).toContain('function handleAuthExpired(');
expect(requestJs).toContain('40100');
expect(requestJs).toContain('登录已过期');
});
});
describe('2. 核销码反查冲突优先级', () => {
it('活动核销码优先未核销、最新记录', () => {
const start = repoCode.indexOf('async queryCheckinCodeInfo(');
const block = repoCode.slice(start, start + 1000);
expect(block).toContain('ORDER BY p.checkin_status ASC, p.created_at DESC');
});
it('订单核销码排除已关闭状态并优先待核销', () => {
const start = repoCode.indexOf('async queryCheckinCodeInfo(');
const block = repoCode.slice(start, start + 2600);
expect(block).toContain("AND status NOT IN ('已取消', '已退款', '已全额退款')");
expect(block).toContain("ORDER BY FIELD(status, '履约中', '服务中', '待确认'");
});
});
});

查看文件

@@ -0,0 +1,65 @@
// 已读取AGENTS.md
import * as fs from 'fs';
import * as path from 'path';
/**
* 第十批回归闸门:订单评价/动态发布与删除的身份收口 + 关键路径可观测性
*/
describe('第十批身份收口与可观测性闸门 (评价 / 动态发布 / 动态删除)', () => {
const rootDir = path.resolve(__dirname, '../../');
const indexCode = fs.readFileSync(path.join(rootDir, 'server/src/index.ts'), 'utf-8');
const repoCode = fs.readFileSync(path.join(rootDir, 'server/src/db/repository.ts'), 'utf-8');
const apiCode = fs.readFileSync(path.join(rootDir, 'pwuniapp/common/api.js'), 'utf-8');
const routeBlock = (marker: string, len = 900) => {
const start = indexCode.indexOf(marker);
expect(start).toBeGreaterThan(-1);
return indexCode.slice(start, start + len);
};
describe('1. 订单评价身份以服务端会话为准', () => {
it('评价路由必须经 resolveSecureUserId 绑定买家身份', () => {
const block = routeBlock("app.post('/api/orders/:id/review'");
expect(block).toContain('resolveSecureUserId(');
expect(block).toContain("'提交订单评价'");
expect(block).toContain('userId: reviewerId');
expect(block).not.toContain('userId: userId ? Number(userId) : undefined');
});
it('仓储层仍保留服务端归属校验(仅下单买家)', () => {
expect(repoCode).toContain('仅下单买家本人可以评价该订单');
});
});
describe('2. 动态发布身份以服务端会话为准', () => {
it('发布路由必须经 resolveSecureUserId 且写库用服务端 uid', () => {
const block = routeBlock("app.post('/api/posts',");
expect(block).toContain("'发布动态'");
expect(block).toContain('repo.createPost({ ...(req.body || {}), userId: uid })');
});
});
describe('3. 动态删除身份以服务端会话为准', () => {
it('删除路由必须经 resolveSecureUserId', () => {
const block = routeBlock("app.delete('/api/posts/:id'");
expect(block).toContain("'删除动态'");
});
});
describe('4. 关键路径不得静默吞错', () => {
it('openid 持久化/预支付解析/用户设置 IO 必须记录错误', () => {
expect(indexCode).toContain("logServerError('login.updateUserOpenid'");
expect(indexCode).toContain("logServerError('prepay.resolveOpenid'");
expect(indexCode).toContain("logServerError('userSettings.write'");
expect(repoCode).toContain("logServerError('getDashboardTodos.reports'");
});
});
describe('5. 小程序端写接口携带真实身份', () => {
it('发布动态/提交评价必须注入当前用户身份', () => {
expect(apiCode).toContain('userId: normalizeUserId(payload && payload.userId)');
expect(apiCode).toContain('userId: normalizeUserId(reviewData.userId)');
expect(apiCode).toContain('const uid = normalizeUserId(userId);');
});
});
});

查看文件

@@ -38,7 +38,7 @@ describe('第三批安全加固闸门 (动态越权删除 / 组局解散提权 /
const start = code.indexOf('export async function deletePost(');
expect(start).toBeGreaterThan(-1);
const block = code.slice(start, start + 400);
expect(block).toContain("uni.getStorageSync('userId') || 1001");
expect(block).toContain('normalizeUserId(userId)');
expect(block).not.toContain('userId = 1001');
});
});

查看文件

@@ -0,0 +1,21 @@
// 已读取AGENTS.md
import * as fs from 'fs';
import * as path from 'path';
/**
* 第三十二批回归闸门:用户设置文件不得位于对外静态目录
*/
describe('第三十二批用户设置文件私有化闸门', () => {
const rootDir = path.resolve(__dirname, '../../');
const indexCode = fs.readFileSync(path.join(rootDir, 'server/src/index.ts'), 'utf-8');
it('设置文件路径必须位于私有 data 目录而非 /uploads', () => {
expect(indexCode).not.toContain("path.resolve(uploadDir, 'user_settings.json')");
expect(indexCode).toContain('const userSettingsDir =');
expect(indexCode).toContain("path.resolve(userSettingsDir, 'user_settings.json')");
});
it('/uploads 仍仅用于静态资源', () => {
expect(indexCode).toContain("app.use('/uploads', express.static(uploadDir))");
});
});

查看文件

@@ -0,0 +1,30 @@
// 已读取AGENTS.md
import * as fs from 'fs';
import * as path from 'path';
/**
* 第四十九批回归闸门:结算/仲裁给大神入账必须唯一命中,禁止昵称模糊重复入账
*/
describe('第四十九批结算入账唯一性闸门', () => {
const rootDir = path.resolve(__dirname, '../../');
const repoCode = fs.readFileSync(path.join(rootDir, 'server/src/db/repository.ts'), 'utf-8');
it('提供唯一解析结算收款用户的私有方法', () => {
expect(repoCode).toContain('async resolveExpertCreditUserId(');
const start = repoCode.indexOf('async resolveExpertCreditUserId(');
const block = repoCode.slice(start, start + 800);
expect(block).toContain('WHERE nickname = ? LIMIT 1');
expect(block).toContain('comp?.user_id');
});
it('不得再使用 id OR nickname 的模糊入账', () => {
expect(repoCode).not.toContain('WHERE id = ? OR nickname = ?');
});
it('完单结算与仲裁部分结算均使用唯一收款用户', () => {
const settle = repoCode.slice(repoCode.indexOf('async completeOrderWithSettlement('));
expect(settle.slice(0, 3000)).toContain('resolveExpertCreditUserId(conn, order, comp)');
const arb = repoCode.slice(repoCode.indexOf('async handleArbitration('));
expect(arb.slice(0, 7000)).toContain('resolveExpertCreditUserId(conn, order, comp)');
});
});

查看文件

@@ -0,0 +1,25 @@
// 已读取AGENTS.md
import * as fs from 'fs';
import * as path from 'path';
/**
* 第二十三批回归闸门:订单初始状态与结算状态闸门
*/
describe('第二十三批结算状态闸门', () => {
const rootDir = path.resolve(__dirname, '../../');
const repoCode = fs.readFileSync(path.join(rootDir, 'server/src/db/repository.ts'), 'utf-8');
it('结算闸门必须拦截未付款(待付款)订单', () => {
const start = repoCode.indexOf('async completeOrderWithSettlement(');
const block = repoCode.slice(start, start + 900);
expect(block).toContain("'待付款'");
expect(block).toContain("'待支付'");
});
it('订单初始状态必须由服务端按支付方式决定', () => {
const start = repoCode.indexOf('async createOrder(');
const block = repoCode.slice(start, start + 4200);
expect(block).toContain("const status = payType === 'BALANCE' ? '履约中' : '待付款';");
expect(block).not.toContain('const status = data.status ||');
});
});

查看文件

@@ -0,0 +1,27 @@
// 已读取AGENTS.md
import * as fs from 'fs';
import * as path from 'path';
/**
* 第五十四批回归闸门:启动配置自检(部署验收)
*/
describe('第五十四批启动自检闸门', () => {
const rootDir = path.resolve(__dirname, '../../');
const indexCode = fs.readFileSync(path.join(rootDir, 'server/src/index.ts'), 'utf-8');
it('提供启动自检并输出就绪状态(不泄露密钥)', () => {
expect(indexCode).toContain('async function logStartupConfigSelfCheck(');
const start = indexCode.indexOf('async function logStartupConfigSelfCheck(');
const block = indexCode.slice(start, start + 1800);
expect(block).toContain('运行模式=');
expect(block).toContain('实盘就绪=');
expect(block).toContain('未设置 ADMIN_TOKEN_SECRET');
// 不得打印密钥值,仅打印是否设置
expect(block).not.toContain('${ADMIN_TOKEN_SECRET}');
expect(block).not.toContain('${USER_TOKEN_SECRET}');
});
it('bootstrap 中调用自检', () => {
expect(indexCode).toContain('await logStartupConfigSelfCheck();');
});
});

查看文件

@@ -0,0 +1,60 @@
// 已读取AGENTS.md
import * as fs from 'fs';
import * as path from 'path';
/**
* 第五十七批回归闸门:微信订阅消息(服务通知)真实下发闭环
*/
describe('第五十七批微信订阅消息闸门', () => {
const rootDir = path.resolve(__dirname, '../../');
const svc = fs.readFileSync(path.join(rootDir, 'server/src/core/notify/wechat-subscribe.service.ts'), 'utf-8');
const indexCode = fs.readFileSync(path.join(rootDir, 'server/src/index.ts'), 'utf-8');
const apiCode = fs.readFileSync(path.join(rootDir, 'pwuniapp/common/api.js'), 'utf-8');
const settings = fs.readFileSync(path.join(rootDir, 'pwuniapp/pages/user/settings.vue'), 'utf-8');
it('提供微信订阅消息下发客户端', () => {
expect(svc).toContain('export function sendSubscribeMessageHttp(');
expect(svc).toContain("cgi-bin/message/subscribe/send");
expect(svc).toContain('export function buildTemplateData(');
});
it('服务端按配置真实下发并接入触发点(字段与所选公共模板对齐)', () => {
expect(indexCode).toContain('async function notifyBySubscribe(');
expect(indexCode).toContain("app.get('/api/message/subscribe-templates'");
expect(indexCode).toContain('sendSubscribeMessageHttp(accessToken');
for (const key of ['orderStatus', 'activityJoin', 'withdrawSuccess']) {
expect(indexCode).toContain(`notifyBySubscribe('${key}'`);
}
// 订单状态提醒模板字段
expect(indexCode).toContain('character_string1:');
expect(indexCode).toContain('phrase2:');
expect(indexCode).toContain('thing3:');
expect(indexCode).toContain('thing4:');
// 提现审核通知模板字段
expect(indexCode).toContain('amount2:');
expect(indexCode).toContain('phrase5:');
expect(indexCode).toContain('time7:');
expect(indexCode).toContain('amount8:');
// 活动报名成功通知模板字段
expect(indexCode).toContain('thing1:');
expect(indexCode).toContain('time2:');
expect(indexCode).toContain('time3:');
// 开始时间兜底:非标准日期回退当前时间,避免 time 字段 47003
expect(indexCode).toContain('function toCnTimeOrNow(');
expect(indexCode).toContain('time3: toCnTimeOrNow(act?.startTime)');
});
it('管理端测试推送在就绪时真实下发', () => {
const start = indexCode.indexOf("app.post('/api/admin/message/test-send'");
const block = indexCode.slice(start, start + 3400);
expect(block).toContain("mode: 'REAL'");
expect(block).toContain("mode: 'SIMULATED'");
});
it('小程序用真实模板ID 请求订阅授权', () => {
expect(apiCode).toContain('export async function getSubscribeTemplates(');
expect(apiCode).toContain('/message/subscribe-templates');
expect(settings).toContain('requestNoticeSubscribe');
expect(settings).not.toContain("'ORDER_STATUS_NOTICE_TMPL'");
});
});

查看文件

@@ -0,0 +1,20 @@
// 已读取AGENTS.md
import * as fs from 'fs';
import * as path from 'path';
/**
* 第四十五批回归闸门:上传接口生产环境需登录
*/
describe('第四十五批上传鉴权闸门', () => {
const rootDir = path.resolve(__dirname, '../../');
const indexCode = fs.readFileSync(path.join(rootDir, 'server/src/index.ts'), 'utf-8');
it('生产环境上传必须要求登录', () => {
const start = indexCode.indexOf('const handleFileUpload =');
expect(start).toBeGreaterThan(-1);
const block = indexCode.slice(start, start + 500);
expect(block).toContain('IS_PRODUCTION');
expect(block).toContain("resolveSecureUserId(req, 0, '上传文件')");
expect(block).toContain('请先登录后再上传');
});
});

查看文件

@@ -0,0 +1,26 @@
// 已读取AGENTS.md
import * as fs from 'fs';
import * as path from 'path';
/**
* 第五十一批回归闸门:管理员用户360画像按 id 精确关联
*/
describe('第五十一批用户360画像关联闸门', () => {
const rootDir = path.resolve(__dirname, '../../');
const repoCode = fs.readFileSync(path.join(rootDir, 'server/src/db/repository.ts'), 'utf-8');
it('订单按 buyer/expert/claimed/companion(user_id) 关联', () => {
const start = repoCode.indexOf('async getUserDetail(');
expect(start).toBeGreaterThan(-1);
const block = repoCode.slice(start, start + 3000);
expect(block).toContain('OR companion_name IN (SELECT name FROM companions WHERE user_id = ? OR id = ?)');
expect(block).not.toContain('WHERE client_name = ? OR companion_name = ?');
});
it('资金流水按 user_id 精确归属', () => {
const start = repoCode.indexOf('async getUserDetail(');
const block = repoCode.slice(start, start + 3200);
expect(block).toContain('WHERE user_id = ?');
expect(block).not.toContain('counterparty LIKE ? OR counterparty LIKE ?');
});
});

查看文件

@@ -0,0 +1,46 @@
// 已读取AGENTS.md
import * as fs from 'fs';
import * as path from 'path';
/**
* 第十三批回归闸门:用户/风控读接口的敏感字段与鉴权收口
*/
describe('第十三批用户读接口隐私闸门 (全量用户 / 360画像 / 风控日志 / 个人资产)', () => {
const rootDir = path.resolve(__dirname, '../../');
const indexCode = fs.readFileSync(path.join(rootDir, 'server/src/index.ts'), 'utf-8');
const routeBlock = (marker: string, len = 1600) => {
const start = indexCode.indexOf(marker);
expect(start).toBeGreaterThan(-1);
return indexCode.slice(start, start + len);
};
describe('1. 管理端专属读接口必须鉴权', () => {
it('全量用户列表仅管理员可见', () => {
expect(indexCode).toContain("app.get('/api/users', requireAdmin");
});
it('用户360画像仅管理员可见', () => {
expect(indexCode).toContain("app.get('/api/users/:id/detail', requireAdmin");
});
it('风控日志仅管理员可见', () => {
expect(indexCode).toContain("app.get('/api/safety/audit-logs', requireAdmin");
});
});
describe('2. 用户基本信息:敏感字段仅本人/管理员可见', () => {
it('GET /api/users/:id 必须做本人/管理员判断并剥离敏感字段', () => {
const block = routeBlock("app.get('/api/users/:id',", 8000);
expect(block).toContain('isVerifiedAdminRequest(req)');
expect(block).toContain('publicUser');
expect(block).not.toContain('phone: user.phone');
expect(block).not.toContain('balance: user.balance');
});
});
describe('3. 个人资产/流水仅本人可读', () => {
it('提现记录与财务流水必须经 resolveSecureUserId', () => {
expect(routeBlock("app.get('/api/users/:id/withdrawals'")).toContain("'查看提现记录'");
expect(routeBlock("app.get('/api/users/:id/finance-records'")).toContain("'查看财务流水'");
});
});
});

查看文件

@@ -0,0 +1,45 @@
// 已读取AGENTS.md
import * as fs from 'fs';
import * as path from 'path';
/**
* 第十五批回归闸门:核销码反查防枚举 + 抢单池客户名脱敏
*/
describe('第十五批核销码与抢单池加固闸门', () => {
const rootDir = path.resolve(__dirname, '../../');
const indexCode = fs.readFileSync(path.join(rootDir, 'server/src/index.ts'), 'utf-8');
const repoCode = fs.readFileSync(path.join(rootDir, 'server/src/db/repository.ts'), 'utf-8');
const apiCode = fs.readFileSync(path.join(rootDir, 'pwuniapp/common/api.js'), 'utf-8');
const routeBlock = (marker: string, len = 1400) => {
const start = indexCode.indexOf(marker);
expect(start).toBeGreaterThan(-1);
return indexCode.slice(start, start + len);
};
describe('1. 核销码反查不得被匿名枚举', () => {
it('必须要求登录并按门票归属方校验', () => {
const block = routeBlock("app.get('/api/verify/query/:code'");
expect(block).toContain("'查询核销码'");
expect(block).toContain('IS_PRODUCTION && !isVerifiedAdminRequest(req)');
expect(block).toContain('无权查看该核销码信息');
});
});
describe('2. 抢单池客户名脱敏', () => {
it('仓储层必须对 client_name 脱敏', () => {
const start = repoCode.indexOf('async getOrdersPool(');
const block = repoCode.slice(start, start + 1400);
expect(block).toContain('maskClientName');
expect(block).not.toContain('userName: r.clientName ||');
});
});
describe('3. 小程序反查携带当前用户身份', () => {
it('queryCheckinCode 必须带 userId', () => {
const start = apiCode.indexOf('export async function queryCheckinCode(');
const block = apiCode.slice(start, start + 300);
expect(block).toContain('/verify/query/${code}?userId=${normalizeUserId()}');
});
});
});

查看文件

@@ -0,0 +1,25 @@
// 已读取AGENTS.md
import * as fs from 'fs';
import * as path from 'path';
/**
* 第五十二批回归闸门:个人资金流水按 user_id 精确归属
*/
describe('第五十二批资金流水归属闸门', () => {
const rootDir = path.resolve(__dirname, '../../');
const repoCode = fs.readFileSync(path.join(rootDir, 'server/src/db/repository.ts'), 'utf-8');
it('个人流水查询仅按 user_id 过滤,不再用 counterparty LIKE', () => {
const start = repoCode.indexOf('async getUserFinanceRecords(');
expect(start).toBeGreaterThan(-1);
const block = repoCode.slice(start, start + 700);
expect(block).toContain('WHERE user_id = ?');
expect(block).not.toContain('counterparty LIKE ?');
expect(block).not.toContain('OR related_id = ?');
});
it('关键资金流水写入时带 user_id', () => {
expect(repoCode).toContain("'钱包余额支付订单', ?, 'OUT'");
expect(repoCode).toContain("'佣金提现', ?, 'OUT'");
});
});

查看文件

@@ -0,0 +1,45 @@
// 已读取AGENTS.md
import * as fs from 'fs';
import * as path from 'path';
/**
* 第五十五批回归闸门:提现「真实微信打款」闭环
*/
describe('第五十五批提现打款闭环闸门', () => {
const rootDir = path.resolve(__dirname, '../../');
const payV3 = fs.readFileSync(path.join(rootDir, 'server/src/core/pay/wechat-pay-v3.ts'), 'utf-8');
const indexCode = fs.readFileSync(path.join(rootDir, 'server/src/index.ts'), 'utf-8');
const repoCode = fs.readFileSync(path.join(rootDir, 'server/src/db/repository.ts'), 'utf-8');
const mysqlCode = fs.readFileSync(path.join(rootDir, 'server/src/db/mysql.ts'), 'utf-8');
it('支付客户端具备商家转账到零钱能力', () => {
expect(payV3).toContain('export async function transferToWallet(');
expect(payV3).toContain("'/v3/fund-app/mch-transfer/transfer-bills'");
expect(payV3).toContain('export async function queryTransferByOutBillNo(');
expect(payV3).toContain('export function encryptSensitiveInfo(');
});
it('提现审核通过按模式真实打款 / 生产未就绪拒绝', () => {
const start = indexCode.indexOf("app.post('/api/finance/withdrawals/:id/review'");
expect(start).toBeGreaterThan(-1);
const block = indexCode.slice(start, start + 4600);
expect(block).toContain('cfg.isReadyForTransfer');
expect(block).toContain('transferToWallet(cfg');
expect(block).toContain('getWithdrawalForTransfer(wdId)');
expect(block).toContain('微信商家转账未就绪');
expect(block).not.toContain('款项已通过微信商户零钱打款'); // 移除误导文案
});
it('提供转账结果回调与最终态落库', () => {
expect(indexCode).toContain("app.post('/api/pay/wechat/transfer-notify'");
expect(repoCode).toContain('async finalizeWithdrawalTransfer(');
expect(repoCode).toContain('async getWithdrawalForTransfer(');
expect(repoCode).toContain('async markWithdrawalTransfer(');
});
it('提现表具备转账/收款人字段', () => {
expect(mysqlCode).toContain('transfer_bill_no');
expect(mysqlCode).toContain('transfer_status');
expect(mysqlCode).toContain('payee_name');
});
});