// 自动转发去重的回归测试。 // // 这个 bug 是生产里真实发生的:模型带附件主动回了一封(311 字节), // session.idle 又把它最后那段话自动转了一封(342 字节), // 收件箱里两封说同一件事,其中只有一封能下载附件。 // // 逻辑放在 lib/relay-dedup.js 而不是 index.js:opencode 把入口模块的每一个 // 导出都当成插件工厂,入口多导出一个 Map 就会让整个插件加载失败 // (实测 "Plugin export is not a function",插件静默不加载,邮件全投不进去)。 import { shouldSkipAutoRelay, noteExplicitSend, explicitSends, addrName, } from '../lib/relay-dedup.js'; let failed = 0; const check = (name, cond, detail = '') => { if (cond) console.log(` 通过 ${name}`); else { console.error(` 失败 ${name}${detail ? ' — ' + detail : ''}`); failed++; } }; console.log('自动转发去重:'); // ---- addrName ---- check('三维地址取名字段', addrName('admin@root.tidy-tiger') === 'admin'); check('纯名字原样返回', addrName('admin') === 'admin'); check('空值不炸', addrName(undefined) === '' && addrName(null) === ''); check('去空白', addrName(' admin @root') === 'admin'); // ---- 没有主动发信记录 → 照常自动转发 ---- check( '本轮没主动发信 → 不跳过(自动转发是默认行为)', shouldSkipAutoRelay(undefined, 'admin', 'mail-1') === false ); // ---- 收件人同名 → 跳过 ---- explicitSends.clear(); noteExplicitSend('ses-1', 'admin@root.tidy-tiger', 'mail-1'); check( '模型已回给同一个人 → 跳过', shouldSkipAutoRelay(explicitSends.get('ses-1'), 'admin', 'mail-1') === true ); check( '收件人写法不同但同名 → 仍跳过(地址带路径/会话段)', shouldSkipAutoRelay(explicitSends.get('ses-1'), 'admin@root', 'mail-1') === true ); // ---- 发给别人 → 不跳过 ---- explicitSends.clear(); noteExplicitSend('ses-2', 'ops@root.new', 'mail-9'); check( '模型主动联系了第三方,但本轮的来信还没回 → 不跳过', shouldSkipAutoRelay(explicitSends.get('ses-2'), 'admin', 'mail-1') === false ); // ---- reply_to 相同 → 跳过(即使收件人名字对不上)---- explicitSends.clear(); noteExplicitSend('ses-3', 'someone-else@root', 'mail-1'); check( '已回过同一封信 → 跳过(reply_to 命中,收件人名字不同也算)', shouldSkipAutoRelay(explicitSends.get('ses-3'), 'admin', 'mail-1') === true ); // ---- 一轮里发了多封 ---- explicitSends.clear(); noteExplicitSend('ses-4', 'ops@root', 'mail-8'); noteExplicitSend('ses-4', 'admin@root', 'mail-1'); const rec4 = explicitSends.get('ses-4'); check('一轮多封都记下', rec4.names.size === 2 && rec4.replyTos.size === 2); check('其中任一命中即跳过', shouldSkipAutoRelay(rec4, 'admin', 'mail-1') === true); check('都不命中则不跳过', shouldSkipAutoRelay(rec4, 'someone', 'mail-99') === false); // ---- 无 sessionID 不记录(防止污染一个 undefined 键)---- explicitSends.clear(); noteExplicitSend(undefined, 'admin@root', 'mail-1'); check('没有 sessionID 时不记录', explicitSends.size === 0); // ---- 空 mailID 不该让 replyTos 命中 ---- explicitSends.clear(); noteExplicitSend('ses-5', 'ops@root', ''); const rec5 = explicitSends.get('ses-5'); check('空 reply_to 不入集合', rec5.replyTos.size === 0); check( '本来要转的邮件没有 id 时只看收件人', shouldSkipAutoRelay(rec5, 'admin', '') === false ); // ---- 不变量:插件入口只能有 default 导出 ---- // // opencode 用 Object.values(mod) 逐个检查每个导出是不是函数, // 多导出一个 Map/常量就抛 "Plugin export is not a function", // 整个插件静默不加载 —— 邮件全都投不进去。实测踩过这个坑。 import { readFileSync } from 'node:fs'; const entry = readFileSync(new URL('../index.js', import.meta.url), 'utf8'); const exports_ = entry.match(/^export\s+(?!default\b).*/gm) || []; check( '入口 index.js 只有 default 导出', exports_.length === 0, exports_.length ? `多出:${exports_.map(l => l.slice(0, 50)).join(' | ')}` : '' ); check('入口确实有 default 导出', /^export default /m.test(entry)); console.log(failed === 0 ? '\n自动转发去重:全部通过' : `\n自动转发去重:${failed} 项失败`); process.exit(failed === 0 ? 0 : 1);