self-chat-callbacks.test.js 3.14 KB
/**
 * 自唤醒回调(`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'/);
  });
});