feat: security hardening, wechat pay v3 platform cert integration, companion certification revocation, and audit closed-loop
这个提交包含在:
@@ -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(22);
|
||||
expect(config.pages.length).toBe(25);
|
||||
|
||||
for (const pageItem of config.pages) {
|
||||
const vueFilePath = path.join(rootDir, 'pwuniapp', `${pageItem.path}.vue`);
|
||||
@@ -1119,6 +1119,120 @@ describe('ContentSecurityService & Compliance Architecture (TDD 微信内容安
|
||||
expect(emojiRegex.test(fs.readFileSync(apiPath, 'utf-8'))).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('24. 独立专属钱包充值页面规格验证 (pages/user/recharge.vue)', () => {
|
||||
const rechargePath = path.join(rootDir, 'pwuniapp', 'pages', 'user', 'recharge.vue');
|
||||
const pagesJsonPath = path.join(rootDir, 'pwuniapp', 'pages.json');
|
||||
const userIndexPath = path.join(rootDir, 'pwuniapp', 'pages', 'user', 'index.vue');
|
||||
|
||||
it('pages.json 必须成功注册 pages/user/recharge 且物理文件必须存在', () => {
|
||||
expect(fs.existsSync(rechargePath)).toBe(true);
|
||||
const pagesJson = JSON.parse(fs.readFileSync(pagesJsonPath, 'utf-8'));
|
||||
const hasPage = pagesJson.pages.some((p: any) => p.path === 'pages/user/recharge');
|
||||
expect(hasPage).toBe(true);
|
||||
});
|
||||
|
||||
it('充值页面必须包含沉浸式导航栏、当前余额看板、档位网格、自定义金额、微信支付通道与提交栏', () => {
|
||||
const content = fs.readFileSync(rechargePath, 'utf-8');
|
||||
expect(content).toContain('custom-nav-bar');
|
||||
expect(content).toContain('钱包充值');
|
||||
expect(content).toContain('handleBack');
|
||||
expect(content).toContain('balance-card');
|
||||
expect(content).toContain('tier-grid');
|
||||
expect(content).toContain('custom-amount');
|
||||
expect(content).toContain('channel-card');
|
||||
expect(content).toContain('微信支付');
|
||||
expect(content).toContain('fixed-submit-bar');
|
||||
expect(content).toContain('rechargeBalance');
|
||||
});
|
||||
|
||||
it('个人中心页面 (pages/user/index.vue) 充值入口必须原生跳转至 /pages/user/recharge 且杜绝弹窗', () => {
|
||||
const content = fs.readFileSync(userIndexPath, 'utf-8');
|
||||
expect(content).toContain("url: '/pages/user/recharge'");
|
||||
const rechargeFuncMatch = content.match(/const handleRecharge\s*=\s*(?:async\s*)?\(\)\s*=>\s*\{[\s\S]*?\n\};/);
|
||||
expect(rechargeFuncMatch).not.toBeNull();
|
||||
expect(rechargeFuncMatch![0]).toContain("url: '/pages/user/recharge'");
|
||||
expect(rechargeFuncMatch![0]).not.toContain('showActionSheet');
|
||||
});
|
||||
|
||||
it('充值页面与测试代码严禁包含任何系统 Emoji 与 showModal 4 字门禁违规', () => {
|
||||
const rechargeContent = fs.readFileSync(rechargePath, 'utf-8');
|
||||
const emojiRegex = /(?:\p{Extended_Pictographic}|\p{Emoji_Presentation}|[\u2600-\u27BF])/u;
|
||||
expect(emojiRegex.test(rechargeContent)).toBe(false);
|
||||
|
||||
const modalMatches = rechargeContent.match(/(?:confirmText|cancelText)\s*:\s*['"]([^'"]+)['"]/g) || [];
|
||||
for (const m of modalMatches) {
|
||||
const text = m.split(':')[1].replace(/['"\s]/g, '');
|
||||
expect(text.length).toBeLessThanOrEqual(4);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('25. 接单大厅大神本人信息与状态动态联动验证 (pages/expert/hall.vue)', () => {
|
||||
const hallPath = path.join(rootDir, 'pwuniapp', 'pages', 'expert', 'hall.vue');
|
||||
|
||||
it('hall.vue 必须物理存在且支持从 Storage 动态加载当前登录大神的真实信息', () => {
|
||||
expect(fs.existsSync(hallPath)).toBe(true);
|
||||
const content = fs.readFileSync(hallPath, 'utf-8');
|
||||
expect(content).toContain("uni.getStorageSync('userId')");
|
||||
expect(content).toContain("getUserProfile");
|
||||
expect(content).toContain("getExpertStats");
|
||||
});
|
||||
|
||||
it('hall.vue 必须在 onShow 中刷新当前登录大神的资料与接单在线状态', () => {
|
||||
const content = fs.readFileSync(hallPath, 'utf-8');
|
||||
expect(content).toContain("EXPERT_RECEIVING_STATUS");
|
||||
expect(content).toContain("onShow");
|
||||
expect(content).toContain("loadHallData");
|
||||
});
|
||||
|
||||
it('立即接单 handleGrabOrder 必须动态绑定当前大神的真实 ID、昵称与头像', () => {
|
||||
const content = fs.readFileSync(hallPath, 'utf-8');
|
||||
expect(content).toContain("expert.value.id");
|
||||
expect(content).toContain("expert.value.nickname");
|
||||
expect(content).toContain("expert.value.avatar");
|
||||
expect(content).not.toContain("companionName: '我 (接单大神)'");
|
||||
});
|
||||
|
||||
it('hall.vue 页面代码与测试严禁包含任何系统 Emoji 与 showModal 4 字门禁违规', () => {
|
||||
const content = fs.readFileSync(hallPath, 'utf-8');
|
||||
const emojiRegex = /(?:\p{Extended_Pictographic}|\p{Emoji_Presentation}|[\u2600-\u27BF])/u;
|
||||
expect(emojiRegex.test(content)).toBe(false);
|
||||
|
||||
const modalMatches = content.match(/(?:confirmText|cancelText)\s*:\s*['"]([^'"]+)['"]/g) || [];
|
||||
for (const m of modalMatches) {
|
||||
const text = m.split(':')[1].replace(/['"\s]/g, '');
|
||||
expect(text.length).toBeLessThanOrEqual(4);
|
||||
}
|
||||
});
|
||||
|
||||
it('派单卡片必须支持点击查看详情面板,且详情面板严禁使用 @click.self 必须 stopPropagation', () => {
|
||||
const content = fs.readFileSync(hallPath, 'utf-8');
|
||||
expect(content).toContain('openOrderDetailModal');
|
||||
expect(content).toContain('showDetailModal');
|
||||
expect(content).toContain('dispatch-detail-mask');
|
||||
expect(content).toContain('dispatch-detail-modal');
|
||||
expect(content).toContain('closeOrderDetailModal');
|
||||
expect(content).toContain('stopPropagation');
|
||||
expect(content).not.toContain('@click.self');
|
||||
});
|
||||
|
||||
it('派单详情弹窗酬金展示必须使用徽章卡片结构并实现货币符号与数字基线横排对齐', () => {
|
||||
const content = fs.readFileSync(hallPath, 'utf-8');
|
||||
expect(content).toContain('reward-badge-card');
|
||||
expect(content).toContain('badge-label');
|
||||
expect(content).toContain('amount-row');
|
||||
expect(content).toContain('curr-mark');
|
||||
expect(content).toContain('reward-num');
|
||||
});
|
||||
|
||||
it('派单详情弹窗单号必须支持脱敏简化展示并支持点击一键复制完整流水号', () => {
|
||||
const content = fs.readFileSync(hallPath, 'utf-8');
|
||||
expect(content).toContain('formatShortOrderNo');
|
||||
expect(content).toContain('copyOrderNo');
|
||||
expect(content).toContain('uni.setClipboardData');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
|
||||
在新工单中引用
屏蔽一个用户