#!/usr/bin/env node /** * WP-WX-01 离线回归:forwardMessage 文本转发真 Frida 分支逻辑 * * 真机铁律:本测试**仅作离线回归**,不替代真机 Frida attach E2E。 * 它从 wechat_hook_v2.js 抽取真实 forwardMessage 源码(不复制、不漂移), * 注入 mock 依赖,验证三条分支: * 1) 文本类(type=1) → _sendMessageInternal 真发送成功 → 返回 text_resend,不走 intent * 2) 非文本 / DB miss → 降级 _intentAction,标 non_text_or_db_miss(不伪造成功) * 3) 真发送未验证(success:false) → 不假成功,降级 intent * 4) 缺参 → 直接 error * * 运行:node sdk/tests/test_forward_message_logic.mjs */ import { readFileSync } from 'node:fs'; import { fileURLToPath } from 'node:url'; import path from 'node:path'; const __dirname = path.dirname(fileURLToPath(import.meta.url)); const JS_PATH = path.join(__dirname, '..', 'agent', 'hook', 'wechat_hook_v2.js'); const src = readFileSync(JS_PATH, 'utf8'); // 抽取 forwardMessage 函数体(花括号配平),避免复制造成漂移 function extractFunc(name) { const re = new RegExp(name + '\\s*:\\s*function\\s*\\([^)]*\\)\\s*\\{'); const m = re.exec(src); if (!m) throw new Error('未找到函数 ' + name); const bodyStart = src.indexOf('{', m.index + m[0].length - 1); let depth = 0; for (let i = bodyStart; i < src.length; i++) { if (src[i] === '{') depth++; else if (src[i] === '}') { depth--; if (depth === 0) { // 返回 "function(params){ ... }" 形式 const argsStart = src.indexOf('(', m.index); const argsEnd = src.indexOf(')', argsStart); const args = src.slice(argsStart + 1, argsEnd); return new Function(args, src.slice(bodyStart + 1, i)); } } } throw new Error('花括号未配平'); } let assertions = 0; function assert(cond, msg) { assertions++; if (!cond) { console.error(' ✗ FAIL: ' + msg); process.exitCode = 1; } else console.log(' ✓ ' + msg); } // ---- mock harness ---- let calls; function resetMocks(dbRows, sendResult, revokeResult) { calls = { execSQL: [], sendInternal: [], intent: [], event: [], revoke: [] }; globalThis.Java = { performNow: (fn) => fn() }; globalThis._execSQL = (sql) => { calls.execSQL.push(sql); return dbRows; }; globalThis._sendMessageInternal = (toId, content, type) => { calls.sendInternal.push({ toId, content, type }); return sendResult; }; globalThis._intentAction = (action, payload, evt, extra) => { calls.intent.push({ action, payload, evt, extra }); return { success: false, action, _intent: true, extra }; }; // revokeMessage 已改走真 Frida RevokeMsgEvent,mock 其返回以验证诚实分支 globalThis._revokeMessageByRevokeMsgEvent = (msgSvrId, row) => { calls.revoke.push({ msgSvrId, row }); return revokeResult || { success: false, error: 'revoke_frida_failed', msg_svr_id: msgSvrId, eligible: true }; }; globalThis.emitEvent = (evt, data) => { calls.event.push({ evt, data }); }; globalThis.String = String; } const forwardMessage = extractFunc('forwardMessage'); console.log('[WP-WX-01] forwardMessage 离线分支回归'); // 1) 文本类真发送成功 resetMocks([{ type: 1, content: '你好转发' }], { success: true, message_id: 'm123', verified: true }); let r = forwardMessage({ msg_svr_id: 's1', to_id: 'wxid_a' }); assert(r.success === true, '文本类转发成功'); assert(r.forward_kind === 'text_resend', '标记 text_resend'); assert(calls.sendInternal.length === 1 && calls.sendInternal[0].content === '你好转发', '走 _sendMessageInternal 真发送'); assert(calls.intent.length === 0, '成功路径不降级 intent'); assert(calls.event.length === 1 && calls.event[0].evt === 'message_forwarded', '发 message_forwarded 事件'); // 2) 非文本(type=3 图片)→ intent 降级,不假成功 resetMocks([{ type: 3, content: '[图片]' }], { success: true }); r = forwardMessage({ msg_svr_id: 's2', to_id: 'wxid_b' }); assert(calls.sendInternal.length === 0, '非文本不调真发送'); assert(calls.intent.length === 1 && calls.intent[0].extra.kind === 'non_text_or_db_miss', '非文本降级 intent (non_text_or_db_miss)'); assert(r._intent === true && r.success === false, '降级 intent 不伪造成功'); // 3) DB miss(无行)→ intent 降级 resetMocks([], { success: true }); r = forwardMessage({ msg_svr_id: 's3', to_id: 'wxid_c' }); assert(calls.intent.length === 1, 'DB miss 降级 intent'); // 4) 文本但真发送未验证(success:false) → 不假成功,降级 intent resetMocks([{ type: 1, content: 'x' }], { success: false, error: 'unverified' }); r = forwardMessage({ msg_svr_id: 's4', to_id: 'wxid_d' }); assert(calls.sendInternal.length === 1, '尝试真发送'); assert(calls.intent.length === 1, '真发送未验证 → 降级 intent,不假成功'); // 5) 缺参 r = forwardMessage({ msg_svr_id: '', to_id: '' }); assert(r.success === false && /缺少/.test(r.error), '缺参直接 error'); // ---- forwardMultiple 批量转发 ---- console.log('[WP-WX-01] forwardMultiple 离线分支回归'); const forwardMultiple = extractFunc('forwardMultiple'); // 6) 全文本 → 全部真发送,forwarded=2 resetMocks([{ type: 1, content: 'a' }], { success: true, message_id: 'm' }); r = forwardMultiple({ msg_svr_ids: ['s1', 's2'], to_id: 'wxid_x' }); assert(r.success === true && r.forwarded === 2 && r.fallback === 0, '全文本批量转发 forwarded=2'); assert(calls.sendInternal.length === 2, '逐条调真发送 2 次'); assert(calls.intent.length === 0, '有成功条不整体降级'); // 7) 全非文本 → forwarded=0 整体降级 intent,不假成功 resetMocks([{ type: 3, content: '[图片]' }], { success: true }); r = forwardMultiple({ msg_svr_ids: ['s1', 's2'], to_id: 'wxid_y' }); assert(r._intent === true && r.success === false, '全非文本整体降级 intent 不假成功'); assert(calls.intent[0].extra.forwarded === 0, 'intent 标 forwarded=0'); // 8) 缺参 r = forwardMultiple({ msg_svr_ids: [], to_id: 'wxid_z' }); assert(r.success === false && /缺少/.test(r.error), 'forwardMultiple 缺参 error'); // ---- revokeMessage 撤回资格预检 ---- console.log('[WP-WX] revokeMessage 资格预检回归'); const revokeMessage = extractFunc('revokeMessage'); // 9) 消息不存在 → 诚实 msg_not_found,不发 intent resetMocks([], null); r = revokeMessage({ msg_svr_id: 's1' }); assert(r.success === false && r.error_code === 'msg_not_found', '消息不存在→msg_not_found'); assert(calls.intent.length === 0, '不存在不发 intent 假成功'); // 10) 非本人发送 → not_own_message resetMocks([{ isSend: 0, createTime: Date.now() }], null); r = revokeMessage({ msg_svr_id: 's2' }); assert(r.success === false && r.error_code === 'not_own_message', '非本人→not_own_message'); // 11) 超 2 分钟窗口 → revoke_window_expired resetMocks([{ isSend: 1, createTime: Date.now() - 200000 }], null); r = revokeMessage({ msg_svr_id: 's3' }); assert(r.success === false && r.error_code === 'revoke_window_expired', '超窗→revoke_window_expired'); // 12) 资格通过(本人+窗口内) → 走真 Frida RevokeMsgEvent,DB 已确认→成功发 message_revoked resetMocks([{ isSend: 1, createTime: Date.now() - 5000 }], null, { success: true, verified: true, msg_svr_id: 's4' }); r = revokeMessage({ msg_svr_id: 's4' }); assert(calls.revoke.length === 1, '资格通过→走真 Frida RevokeMsgEvent'); assert(r.success === true && r.verified === true, '真撤回 DB 确认→成功'); assert(calls.event.length === 1 && calls.event[0].evt === 'message_revoked', '发 message_revoked 事件'); // 12b) 资格通过但真 RPC 未 DB 确认 → 诚实失败,不假成功、不发事件 resetMocks([{ isSend: 1, createTime: Date.now() - 5000 }], null, { success: false, error: 'revoke_frida_failed', eligible: true }); r = revokeMessage({ msg_svr_id: 's4b' }); assert(calls.revoke.length === 1, '资格通过→尝试真 Frida 撤回'); assert(r.success === false && calls.event.length === 0, '真撤回未确认→诚实失败不发事件'); // 13) 缺参 r = revokeMessage({ msg_svr_id: '' }); assert(r.success === false && /缺少/.test(r.error), 'revokeMessage 缺参 error'); console.log(`\n[done] 断言 ${assertions} 条,exit=${process.exitCode || 0}`);