Files

55 lines
2.4 KiB
TypeScript

/**
* 验证 stdio 模式的标准输出只包含合法 JSON-RPC 消息,不混入诊断文本。
*
* @packageDocumentation
*/
import assert from "node:assert/strict";
import { spawn } from "node:child_process";
import path from "node:path";
import test from "node:test";
import { fileURLToPath } from "node:url";
test("stdio protocol mode writes only JSON-RPC messages to stdout", async () => {
const packageRoot = path.resolve(fileURLToPath(new URL("..", import.meta.url)));
const child = spawn(process.execPath, ["--import", "tsx", "test/stdioFixture.ts"], {
cwd: packageRoot,
shell: false,
windowsHide: true,
stdio: ["pipe", "pipe", "pipe"],
});
let stdout = "";
let stderr = "";
child.stdout.setEncoding("utf8").on("data", (chunk) => { stdout += String(chunk); });
child.stderr.setEncoding("utf8").on("data", (chunk) => { stderr += String(chunk); });
const messages = [
{ jsonrpc: "2.0", id: 1, method: "initialize", params: { protocolVersion: "2025-11-25", capabilities: {}, clientInfo: { name: "stdio-test", version: "1.0.0" } } },
{ jsonrpc: "2.0", method: "notifications/initialized" },
{ jsonrpc: "2.0", id: 2, method: "tools/list", params: { cursor: "next" } },
];
child.stdin.write(messages.map((message) => JSON.stringify(message)).join("\n") + "\n");
await waitFor(() => stdout.split(/\r?\n/).filter(Boolean).some((line) => {
try { return (JSON.parse(line) as { id?: number }).id === 2; } catch { return false; }
}), 3000);
child.stdin.end();
await new Promise<void>((resolve) => {
const timeout = setTimeout(() => { child.kill(); resolve(); }, 1000);
child.once("close", () => { clearTimeout(timeout); resolve(); });
});
const lines = stdout.split(/\r?\n/).filter(Boolean);
assert.ok(lines.length >= 2);
const parsed = lines.map((line) => JSON.parse(line) as { jsonrpc: string; id?: number; result?: unknown });
assert.ok(parsed.every((message) => message.jsonrpc === "2.0"));
assert.ok(parsed.some((message) => message.id === 1));
assert.ok(parsed.some((message) => message.id === 2));
assert.equal(stderr, "");
});
async function waitFor(predicate: () => boolean, timeoutMs: number): Promise<void> {
const started = Date.now();
while (!predicate()) {
if (Date.now() - started > timeoutMs) throw new Error("timed out waiting for stdio response");
await new Promise((resolve) => setTimeout(resolve, 10));
}
}