import { test } from 'node:test'; import assert from 'node:assert/strict'; import { formatAddress, roleOf, replyAddressFor, selfAddressFor, participantsOfMail, } from '../lib/addressing.js'; // 地址拼错不会报错,只会投到别处 —— 所以这一组测试全部落在 // 「拼出来的东西还能不能被正确解析回三段」上。 test('formatAddress: 空 path 仍保留 @ 与 .', () => { // 生产事故:朴素拼接得到 admin.silent-harbor,没有 @, // 整串被 ParseAddress 当成名字,session 位静默丢失。 assert.equal(formatAddress('admin', '', 'silent-harbor'), 'admin@.silent-harbor'); }); test('formatAddress: 省略 session 位', () => { assert.equal(formatAddress('dsh', '/home/program/agentmail', ''), 'dsh@/home/program/agentmail'); // 名字与 path 都有但都不带会话 → 默认会话语义 assert.equal(formatAddress('dsh', '', ''), 'dsh'); }); test('formatAddress: path 含 . 与 / 时仍按最后一个 . 切', () => { // path 里允许 . 与 /,切分靠最后一个 . —— 拼出来的必须满足这个约定 const addr = formatAddress('bot', '/srv/app.v2', 'fix-leak'); assert.equal(addr, 'bot@/srv/app.v2.fix-leak'); assert.equal(addr.slice(addr.lastIndexOf('.') + 1), 'fix-leak'); }); test('formatAddress: 名字为空返回空串而不是残缺地址', () => { // 返回 "@/path.alias" 会被投递端当成缺名字报错, // 但那是在很后面才发现;这里直接给空串让调用方立刻看出没法拼。 assert.equal(formatAddress('', '/p', 'a'), ''); assert.equal(formatAddress(null, '/p', 'a'), ''); }); test('formatAddress: 去掉首尾空白', () => { assert.equal(formatAddress(' dsh ', ' /home ', ' alias '), 'dsh@/home.alias'); }); const ccMail = { from_name: 'admin', to_name: 'dsh', to_workspace: '/home/program/llmsproxy', cc_list: [{ name: 'opencode', path: '/home', session: 'new', raw: 'opencode@/home.new' }], session_alias: 'silent-harbor', }; test('roleOf: 区分主收件人与抄送方', () => { // 被抄送方与主收件人职责不同:线上那封联调邮件里 dsh 负责汇报、 // opencode 只提供信息。不区分身份两方都会以为自己是负责人。 assert.equal(roleOf(ccMail, 'dsh'), 'to'); assert.equal(roleOf(ccMail, 'opencode'), 'cc'); assert.equal(roleOf(ccMail, 'someone-else'), 'unknown'); }); test('roleOf: 名字为空时不猜', () => { assert.equal(roleOf(ccMail, ''), 'unknown'); assert.equal(roleOf(ccMail, undefined), 'unknown'); }); test('replyAddressFor: 用会话别名而非原地址的 .new', () => { // 关键回归:把 .new 原样当回信地址会再建一条平行会话。 const addr = replyAddressFor(ccMail); assert.equal(addr, 'admin@.silent-harbor'); assert.ok(!addr.endsWith('.new'), '回信地址不得以 .new 结尾'); }); test('replyAddressFor: 发件人一侧不带 path', () => { // Agent 回信时 from_workspace 存的是 Agent 名而不是路径, // 拿它拼会得到 dsh@dsh.alias —— 投不出去。 const mail = { from_name: 'dsh', from_workspace: 'dsh', session_alias: 'x' }; assert.equal(replyAddressFor(mail), 'dsh@.x'); }); test('replyAddressFor: 无别名时退回默认会话形式', () => { const mail = { from_name: 'admin', session_alias: '' }; const addr = replyAddressFor(mail); assert.equal(addr, 'admin'); // 调用方靠有没有 . 判断这是不是「投回同一条会话」 assert.ok(!addr.includes('.'), '默认会话形式不含 session 位'); }); test('selfAddressFor: 抄送方取自己那个地址的 path', () => { // to_workspace 是主收件人的工作目录。抄送方拿它当自己的 path, // 「我是谁」这句话就指向了别人的目录。 assert.equal(selfAddressFor(ccMail, 'opencode'), 'opencode@/home.silent-harbor'); assert.equal(selfAddressFor(ccMail, 'dsh'), 'dsh@/home/program/llmsproxy.silent-harbor'); }); test('participantsOfMail: 抄送方的 path 是自己那个', () => { const parts = participantsOfMail(ccMail, 'dsh'); const byName = Object.fromEntries(parts.map(p => [p.name, p])); assert.equal(byName.opencode.path, '/home'); assert.equal(byName.opencode.address, 'opencode@/home.silent-harbor'); assert.equal(byName.dsh.path, '/home/program/llmsproxy'); // 发件人 path 留空,理由同 replyAddressFor assert.equal(byName.admin.address, 'admin@.silent-harbor'); }); test('participantsOfMail: 地址一律用会话别名,不带 .new', () => { // cc_list 里原本记的是 opencode@/home.new。参与方地址必须换成别名, // 否则「回给抄收方」这个动作每次都会新开会话。 for (const p of participantsOfMail(ccMail, 'dsh')) { assert.ok(!p.address.endsWith('.new'), `${p.name} 的地址仍是 .new: ${p.address}`); } }); test('participantsOfMail: 自己被标记而不是被剔除', () => { // 剔掉的话模型无法确认这封信是不是也发给了自己, // 也就无法判断自己该不该回。 const parts = participantsOfMail(ccMail, 'opencode'); const me = parts.find(p => p.name === 'opencode'); assert.ok(me, '自己应出现在参与方列表里'); assert.equal(me.is_self, true); assert.equal(parts.filter(p => p.is_self).length, 1); }); test('participantsOfMail: 角色齐全且顺序为 from → to → cc', () => { // 主收件人稳定排在抄送方之前,模型据此判断谁是负责人、谁是配合方 const parts = participantsOfMail(ccMail, 'dsh'); assert.deepEqual(parts.map(p => p.role), ['from', 'to', 'cc']); }); test('participantsOfMail: 无抄送时只有两方', () => { const mail = { from_name: 'admin', to_name: 'dsh', to_workspace: '/w', session_alias: 'a' }; const parts = participantsOfMail(mail, 'dsh'); assert.equal(parts.length, 2); }); test('participantsOfMail: 跳过空名字条目', () => { // cc_list 里出现空对象(历史数据或解析残缺)不该产出一个 address 为空的参与方 const mail = { from_name: 'admin', to_name: 'dsh', to_workspace: '/w', cc_list: [{ name: '', path: '/x' }, {}], session_alias: 'a', }; const parts = participantsOfMail(mail, 'dsh'); assert.equal(parts.length, 2); for (const p of parts) assert.notEqual(p.address, ''); });