import { test } from 'node:test'; import assert from 'node:assert/strict'; import { isProposableAlias, appendRenameProposal, renameProposalNote, } from '../lib/rename-proposal.js'; // 这一组测试钉住的是「插件拼的标记与服务端正则逐字符对应」。 // 服务端那条正则在 server/internal/handler/rename_proposal.go: // // 拼错不会报错 —— 邮件照常发出,提议凭空消失。 /** 服务端正则的等价实现,用来验证我们拼出来的标记真的能被摘出来。 */ const SERVER_RE = //s; test('isProposableAlias: 合法别名', () => { assert.equal(isProposableAlias('fix-login-leak'), true); assert.equal(isProposableAlias('修复登录态泄漏'), true, '中文别名合法'); assert.equal(isProposableAlias('v2_migration'), true); }); test('isProposableAlias: new 是寻址保留字', () => { // `.new` 是「强制新建会话」的动作,别名叫 new 会让地址无从解释 assert.equal(isProposableAlias('new'), false); }); test('isProposableAlias: 拒绝与三维地址冲突的字符', () => { // 这四个字符都会让 name@path.session 的切分产生歧义 assert.equal(isProposableAlias('a.b'), false, '. 是 session 位分隔符'); assert.equal(isProposableAlias('a/b'), false, '/ 出现在 path 位'); assert.equal(isProposableAlias('a@b'), false, '@ 是 name/path 分隔符'); assert.equal(isProposableAlias('a b'), false, '空白'); assert.equal(isProposableAlias('a\tb'), false, '制表符也算空白'); }); test('isProposableAlias: 拒绝双引号', () => { // 双引号是标记自身的定界符,含它会把标记截断成非法形式 assert.equal(isProposableAlias('say"hi'), false); }); test('isProposableAlias: 空与空白视为没提', () => { assert.equal(isProposableAlias(''), false); assert.equal(isProposableAlias(' '), false); assert.equal(isProposableAlias(undefined), false); assert.equal(isProposableAlias(null), false); }); test('isProposableAlias: 超过 128 字节按字节算', () => { // 服务端是 VARCHAR(128)。中文一个字 3 字节,43 字 = 129 字节 assert.equal(isProposableAlias('a'.repeat(128)), true); assert.equal(isProposableAlias('a'.repeat(129)), false); assert.equal(isProposableAlias('汉'.repeat(42)), true, '126 字节'); assert.equal(isProposableAlias('汉'.repeat(43)), false, '129 字节'); }); test('标记能被服务端正则摘出来', () => { const { body, proposed } = appendRenameProposal('已定位到问题。', 'fix-login-leak', '登录态泄漏'); assert.equal(proposed, true); const m = SERVER_RE.exec(body); assert.ok(m, '服务端正则必须匹配得上'); assert.equal(m[1], 'fix-login-leak'); assert.equal(m[2], '登录态泄漏'); }); test('没有理由时整个 reason 属性都不写', () => { // 写成 reason="" 会让服务端存一个空理由,界面提示条就少了那句解释 const { body } = appendRenameProposal('正文', 'fix-leak'); assert.doesNotMatch(body, /reason=/); const m = SERVER_RE.exec(body); assert.equal(m[1], 'fix-leak'); assert.equal(m[2], undefined); }); test('理由里的双引号被去掉而不是转义', () => { // HTML 注释里没有转义机制,留着会截断标记 const { body } = appendRenameProposal('正文', 'fix-leak', '他说"这是泄漏"'); const m = SERVER_RE.exec(body); assert.ok(m); assert.equal(m[2], '他说这是泄漏'); }); test('原正文完整保留在标记之前', () => { const original = '第一行\n\n第二行'; const { body } = appendRenameProposal(original, 'fix-leak'); assert.ok(body.startsWith(original), '正文不得被改写'); }); test('别名不合法时原样返回,不追加标记', () => { const { body, proposed } = appendRenameProposal('正文', 'a.b'); assert.equal(proposed, false); assert.equal(body, '正文'); assert.doesNotMatch(body, /agentmail:rename-session/); }); test('不给别名时正文完全不变', () => { const { body, proposed } = appendRenameProposal('正文', ''); assert.equal(proposed, false); assert.equal(body, '正文'); }); test('renameProposalNote: 用服务端回的别名,不是本地提议的', () => { // 服务端跑 normalizeAlias:非法字符换 -、new 变 session-new、超长截断。 // 回显本地值会让模型记住一个不存在的名字,之后拿它寻址就 404。 const note = renameProposalNote('fix-login-leak', 'fix.login.leak', true); assert.match(note, /fix-login-leak/); assert.match(note, /规范化/, '要告诉模型名字被改写过'); assert.match(note, /确认/); assert.match(note, /原别名/, '要明确说在那之前用哪个'); }); test('renameProposalNote: 服务端别名与提议一致时不提规范化', () => { const note = renameProposalNote('fix-leak', 'fix-leak', true); assert.match(note, /fix-leak/); assert.doesNotMatch(note, /规范化/); }); test('renameProposalNote: 本地判非法时说清为什么', () => { const note = renameProposalNote('', 'a.b', false); assert.match(note, /未提交/); assert.match(note, /a\.b/); }); test('renameProposalNote: 标记发出但服务端没接受', () => { // 本地校验比服务端宽的情况(例如服务端加了新约束)—— // 不能沉默,否则模型以为提议成功了 const note = renameProposalNote('', 'somealias', true); assert.match(note, /未被服务端接受/); assert.match(note, /somealias/); }); test('renameProposalNote: 没提议时不产生噪音', () => { assert.equal(renameProposalNote('', '', false), ''); assert.equal(renameProposalNote(undefined, undefined, false), ''); });