/** * 自唤醒回调(`selfChat`)的两条不变式。 * * 背景:`/task/dispatch`、`/task/report`、`task/transition` 等六处都要「服务端自己给某个 * agent 发一条 chat」。它们原来各自写死 `hostname:'localhost', port: systemConfig.port` * (3010)—— 单测把路由挂到 `app.listen(0)` 的临时端口上,回调却照样打进**线上 3010**: * 每跑一次汇报用例就给真 CEO 发一条 chat、起一个常驻 claude CLI 子进程、留一个 * `callback-T-*` 会话目录。症状完全静默(测试全绿),只有进程数和内存在涨。 * * 抽成 `selfChat` 之后要盯两件事: * 1. **不许再有第二处自己拼 http.request 打 systemConfig.port** —— 那就是分叉的开始; * 2. **不许留下 `chatReq.write(postData)` 这类孤儿调用** —— 改造时删了声明却留下调用, * `node --check` 一样通过(是运行时 ReferenceError 不是语法错),而它在 * `setImmediate` 里抛就是未捕获异常、直接杀进程。实测这次改造就留下了两处。 */ import { describe, it, expect } from 'vitest'; import fs from 'fs'; import path from 'path'; const FILE = path.resolve(__dirname, '../../routes/agent-chat.js'); const src = fs.readFileSync(FILE, 'utf-8'); const lines = src.split('\n'); describe('自唤醒回调', () => { it('样本非空 —— 路径写错时不许退化成永远通过', () => { expect(src.length).toBeGreaterThan(1000); expect(src).toContain('function selfChat('); }); it('chatReq / postData 只出现在 selfChat 内部,没有孤儿调用', () => { // selfChat 的函数体范围 const start = lines.findIndex((l) => l.includes('function selfChat(')); expect(start).toBeGreaterThan(-1); let end = start; for (let i = start + 1; i < lines.length; i++) { if (lines[i] === '}') { end = i; break; } } const offenders = []; lines.forEach((l, i) => { if (i >= start && i <= end) return; // 函数体内是正当用法 if (/\bchatReq\b|\bpostData\b/.test(l)) offenders.push(`${i + 1}: ${l.trim()}`); }); expect(offenders).toEqual([]); }); it('没有第二处自己拼 http.request 打 systemConfig.port', () => { const start = lines.findIndex((l) => l.includes('function selfChat(')); let end = start; for (let i = start + 1; i < lines.length; i++) { if (lines[i] === '}') { end = i; break; } } const offenders = []; lines.forEach((l, i) => { if (i >= start && i <= end) return; // 「自己拼端口去打自己」的形状:port: systemConfig.port // 跳过注释行 —— selfChat 上方那段说明**引用**了这个写法来解释它为什么被换掉, // 那是文档不是代码。第一版没跳,把注释当成漏改报了红。 if (/^\s*(\/\/|\*|\/\*)/.test(l)) return; if (/port:\s*systemConfig\.port/.test(l)) offenders.push(`${i + 1}: ${l.trim()}`); }); expect(offenders).toEqual([]); }); it('测试环境必须短路 —— 否则单测会打进线上端口', () => { expect(src).toMatch(/process\.env\.VITEST|NODE_ENV === 'test'/); }); });