import { describe, it, expect } from 'vitest'; import { formatAddress, mailCounterpart, sessionCounterpart, sessionReplyTarget, mailReplyTarget, replyAllCC } from '../../src/lib/replyTarget'; import type { Mail, Session } from '../../src/types'; /** * 「这封回复该发给谁」。 * * 锁的是一次生产事故:判据写死 `from_name === 'human'`(单用户时代的遗留), * 多用户下登录名是 jianf,判据恒为假 → 对端取成 from_name(自己)→ 信发给自己。 * 数据库里的链条: * pi → jianf 权限请求 * jianf → pi Re: 权限请求 (对的,锚点是 pi 发来的) * pi → jianf 权限请求 * jianf → jianf Re: Re: 权限请求 (错的,锚点是自己发的) */ let seq = 0; function mail(over: Partial = {}): Mail { seq += 1; return { mail_id: `m${String(seq).padStart(3, '0')}`, session_id: 's1', parent_mail_id: null, from_name: 'pi', from_workspace: '/home/program/llmsproxy', to_name: 'jianf', to_workspace: '', cc_list: [], subject: '主题', body: '正文', mail_type: 'normal', permission_options: null, permission_result: null, status: 'read', created_at: `2026-09-03T10:${String(seq % 60).padStart(2, '0')}:00Z`, ...over }; } const session = (alias: string | null): Session => ({ session_id: 's1', session_alias: alias, from_agent: 'jianf', subject: '关于llmsproxy工程的联合审查', status: 'active', created_at: '2026-09-03T09:00:00Z', updated_at: '2026-09-03T10:00:00Z' }); describe('formatAddress', () => { it('三段齐全', () => { expect(formatAddress('pi', '/home', 'deploy')).toBe('pi@/home.deploy'); }); it('无会话段时省略', () => { expect(formatAddress('pi', '/home')).toBe('pi@/home'); expect(formatAddress('pi', '/home', null)).toBe('pi@/home'); }); it('path 为空仍保留 @(人类没有工作目录)', () => { expect(formatAddress('jianf', '')).toBe('jianf@'); }); it('名字为空给空串而不是拼出 @', () => { expect(formatAddress('', '/home')).toBe(''); }); }); describe('mailCounterpart', () => { it('别人发来的 → 回给发件人', () => { const m = mail({ from_name: 'pi', to_name: 'jianf' }); expect(mailCounterpart(m, 'jianf')).toEqual({ name: 'pi', path: '/home/program/llmsproxy' }); }); it('我发出的 → 回给收件人(不是回给自己)', () => { const m = mail({ from_name: 'jianf', from_workspace: '', to_name: 'pi', to_workspace: '/home/program/llmsproxy' }); // 这一条就是生产 bug:原代码在这里返回 jianf expect(mailCounterpart(m, 'jianf')).toEqual({ name: 'pi', path: '/home/program/llmsproxy' }); }); it('登录名未知时退化为回给发件人,不会回给自己', () => { const m = mail({ from_name: 'pi', to_name: 'jianf' }); expect(mailCounterpart(m, '').name).toBe('pi'); }); it('不把 human 当特殊值', () => { // 老判据写死 human;现在它只是个普通名字 const m = mail({ from_name: 'human', to_name: 'pi' }); expect(mailCounterpart(m, 'jianf').name).toBe('human'); expect(mailCounterpart(m, 'human').name).toBe('pi'); }); }); describe('sessionCounterpart', () => { it('按会话定对端,不受最后一封是谁发的影响', () => { const mails = [ mail({ from_name: 'jianf', from_workspace: '', to_name: 'pi', created_at: '2026-09-03T09:00:00Z' }), mail({ from_name: 'pi', to_name: 'jianf', created_at: '2026-09-03T09:30:00Z' }), // 最后一封是我自己发的 —— 原代码在这里会把自己当对端 mail({ from_name: 'jianf', from_workspace: '', to_name: 'jianf', created_at: '2026-09-03T10:00:00Z' }) ]; expect(sessionCounterpart(mails, 'jianf')?.name).toBe('pi'); }); it('取首个非我参与方:后来被抄送进来的第三方不抢位置', () => { const mails = [ mail({ from_name: 'jianf', from_workspace: '', to_name: 'pi', created_at: '2026-09-03T09:00:00Z' }), mail({ from_name: 'dsh', from_workspace: '/opt', to_name: 'jianf', created_at: '2026-09-03T09:30:00Z' }) ]; expect(sessionCounterpart(mails, 'jianf')?.name).toBe('pi'); }); it('补上首次出现时缺失的 path', () => { const mails = [ // 首封的 to_workspace 是空的 mail({ from_name: 'jianf', from_workspace: '', to_name: 'pi', to_workspace: '', created_at: '2026-09-03T09:00:00Z' }), // 后一封才带上目录 mail({ from_name: 'pi', from_workspace: '/home/program/llmsproxy', to_name: 'jianf', created_at: '2026-09-03T09:30:00Z' }) ]; // 同名 Agent 在不同目录是不同的活,path 不能丢 expect(sessionCounterpart(mails, 'jianf')).toEqual({ name: 'pi', path: '/home/program/llmsproxy' }); }); it('同刻邮件用 mail_id 定序,结果稳定', () => { const ts = '2026-09-03T09:00:00Z'; const a = mail({ mail_id: 'aaa', from_name: 'jianf', from_workspace: '', to_name: 'pi', created_at: ts }); const z = mail({ mail_id: 'zzz', from_name: 'jianf', from_workspace: '', to_name: 'dsh', created_at: ts }); expect(sessionCounterpart([a, z], 'jianf')?.name).toBe(sessionCounterpart([z, a], 'jianf')?.name); }); it('全是自己的会话返回 null 而不是自己', () => { const mails = [mail({ from_name: 'jianf', from_workspace: '', to_name: 'jianf' })]; expect(sessionCounterpart(mails, 'jianf')).toBeNull(); }); it('空会话返回 null', () => { expect(sessionCounterpart([], 'jianf')).toBeNull(); }); it('created_at 解析失败不影响确定性', () => { const bad = mail({ mail_id: 'bad', from_name: 'jianf', from_workspace: '', to_name: 'pi', created_at: '不是时间' }); const good = mail({ mail_id: 'good', from_name: 'jianf', from_workspace: '', to_name: 'dsh', created_at: '2026-09-03T09:00:00Z' }); expect(sessionCounterpart([bad, good], 'jianf')?.name).toBe( sessionCounterpart([good, bad], 'jianf')?.name ); }); }); describe('sessionReplyTarget', () => { it('带上会话别名:不带会落到该 Agent 的默认会话', () => { const mails = [ mail({ from_name: 'jianf', from_workspace: '', to_name: 'pi', to_workspace: '/home/program/llmsproxy' }) ]; expect(sessionReplyTarget(mails, session('pi-关于llmsproxy工程的联合审查'), 'jianf')).toBe( 'pi@/home/program/llmsproxy.pi-关于llmsproxy工程的联合审查' ); }); it('会话未命名时省略会话段', () => { const mails = [ mail({ from_name: 'jianf', from_workspace: '', to_name: 'pi', to_workspace: '/home' }) ]; expect(sessionReplyTarget(mails, session(null), 'jianf')).toBe('pi@/home'); }); it('生产链条重现:回复自己发的那封仍指向 pi', () => { const mails = [ mail({ from_name: 'pi', to_name: 'jianf', created_at: '2026-09-03T10:53:12Z' }), mail({ from_name: 'jianf', from_workspace: '', to_name: 'pi', to_workspace: '/home/program/llmsproxy', created_at: '2026-09-03T10:53:39Z' }), mail({ from_name: 'pi', to_name: 'jianf', created_at: '2026-09-03T10:55:13Z' }), mail({ from_name: 'jianf', from_workspace: '', to_name: 'jianf', created_at: '2026-09-03T10:56:34Z' }) ]; const target = sessionReplyTarget(mails, session('pi-关于llmsproxy工程的联合审查'), 'jianf'); expect(target.startsWith('pi@')).toBe(true); expect(target.startsWith('jianf@')).toBe(false); }); it('找不到对端时给空串(调用方据此禁用发送)', () => { expect(sessionReplyTarget([], session('x'), 'jianf')).toBe(''); }); }); describe('mailReplyTarget', () => { it('单封视图带上该封的会话别名', () => { const m = mail({ from_name: 'pi', to_name: 'jianf', session_alias: 'deploy-review' }); expect(mailReplyTarget(m, 'jianf')).toBe('pi@/home/program/llmsproxy.deploy-review'); }); }); describe('replyAllCC', () => { it('去掉自己与主收件人', () => { const m = mail({ from_name: 'pi', from_workspace: '/home', to_name: 'jianf', to_workspace: '', cc_list: [{ name: 'dsh', path: '/opt', session: '', raw: 'dsh@/opt' }] }); // 主收件人是 pi(回复对象),自己是 jianf —— 都不该出现在抄送里 expect(replyAllCC(m, 'jianf', 'pi')).toEqual(['dsh@/opt']); }); it('自己是 jianf 时不会把自己抄送进去(老判据只挡 human)', () => { const m = mail({ from_name: 'pi', from_workspace: '/home', to_name: 'jianf', to_workspace: '' }); expect(replyAllCC(m, 'jianf', 'pi')).toEqual([]); }); it('cc_list 取 raw 保留会话段', () => { const m = mail({ from_name: 'pi', from_workspace: '/home', to_name: 'jianf', cc_list: [{ name: 'dsh', path: '/opt', session: 'audit', raw: 'dsh@/opt.audit' }] }); // 重新拼 name@path 会丢掉 .audit expect(replyAllCC(m, 'jianf', 'pi')).toEqual(['dsh@/opt.audit']); }); it('同一个人既在 to 又在 cc 时只出现一次', () => { const m = mail({ from_name: 'pi', from_workspace: '/home', to_name: 'dsh', to_workspace: '/opt', cc_list: [{ name: 'dsh', path: '/opt', session: '', raw: 'dsh@/opt' }] }); expect(replyAllCC(m, 'jianf', 'pi')).toEqual(['dsh@/opt']); }); });