mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-25 20:05:46 -06:00
54ebb307cd
Close terminal SSE and stateful notification streams through the owning lifecycle, and reap stdio/QA process groups before exact authority is discarded.\n\nRefs #126098, #126099, #126100.
85 lines
3.2 KiB
TypeScript
85 lines
3.2 KiB
TypeScript
import fs from "node:fs/promises";
|
|
import os from "node:os";
|
|
import path from "node:path";
|
|
import { afterEach, describe, expect, it, vi } from "vitest";
|
|
import { useAutoCleanupTempDirTracker } from "../../../helpers/temp-dir.js";
|
|
import {
|
|
createChildEnv,
|
|
parseNodeMcpTextRecord,
|
|
processIsAlive,
|
|
startHttpFixture,
|
|
stopChild,
|
|
} from "./gateway-node-mcp.test-support.js";
|
|
|
|
const tempDirs = useAutoCleanupTempDirTracker(afterEach);
|
|
|
|
describe("gateway node MCP fixture ownership", () => {
|
|
it.each([
|
|
["direct", (payload: object) => payload],
|
|
["node.invoke", (payload: object) => ({ ok: true, payload })],
|
|
])("parses %s MCP text records", (_label, wrap) => {
|
|
const fact = { label: "node-stdio", marker: "ready", pid: 42 };
|
|
expect(
|
|
parseNodeMcpTextRecord(wrap({ content: [{ type: "text", text: JSON.stringify(fact) }] })),
|
|
).toEqual(fact);
|
|
});
|
|
|
|
it("kills a spawned fixture when readiness validation fails", async () => {
|
|
const root = tempDirs.make("mcp-fixture-startup-failure-");
|
|
const fixturePath = path.join(root, "invalid-fixture.mjs");
|
|
const pidPath = path.join(root, "fixture.pid");
|
|
await fs.writeFile(
|
|
fixturePath,
|
|
`import fs from "node:fs"; fs.writeFileSync(${JSON.stringify(pidPath)}, String(process.pid)); console.log(JSON.stringify({type:"wrong"})); setInterval(() => {}, 1000);`,
|
|
"utf8",
|
|
);
|
|
|
|
await expect(
|
|
startHttpFixture({
|
|
fixturePath,
|
|
labelPrefix: "node",
|
|
env: createChildEnv({ home: root, tempDir: os.tmpdir() }),
|
|
}),
|
|
).rejects.toThrow("invalid readiness");
|
|
const pid = Number(await fs.readFile(pidPath, "utf8"));
|
|
try {
|
|
await vi.waitFor(() => expect(processIsAlive(pid)).toBe(false), { timeout: 1_000 });
|
|
} finally {
|
|
if (processIsAlive(pid)) {
|
|
process.kill(pid, "SIGKILL");
|
|
}
|
|
}
|
|
expect(processIsAlive(pid)).toBe(false);
|
|
});
|
|
|
|
it("kills task-owned fixture descendants when stopping the captured root", async () => {
|
|
const root = tempDirs.make("mcp-fixture-descendant-cleanup-");
|
|
const fixturePath = path.join(root, "fixture.mjs");
|
|
const descendantPidPath = path.join(root, "descendant.pid");
|
|
await fs.writeFile(
|
|
fixturePath,
|
|
`import {spawn} from "node:child_process"; import fs from "node:fs"; const child=spawn(process.execPath,["-e","setInterval(()=>{},1000)"],{stdio:"ignore"}); fs.writeFileSync(${JSON.stringify(descendantPidPath)},String(child.pid)); console.log(JSON.stringify({type:"openclaw-mcp-parity-ready",urls:{streamableHttp:"http://127.0.0.1/mcp",sse:"http://127.0.0.1/sse"}})); setInterval(()=>{},1000);`,
|
|
"utf8",
|
|
);
|
|
|
|
const fixture = await startHttpFixture({
|
|
fixturePath,
|
|
labelPrefix: "node",
|
|
env: createChildEnv({ home: root, tempDir: os.tmpdir() }),
|
|
});
|
|
const descendantPid = Number(await fs.readFile(descendantPidPath, "utf8"));
|
|
try {
|
|
expect(processIsAlive(descendantPid)).toBe(true);
|
|
|
|
await stopChild(fixture);
|
|
|
|
await vi.waitFor(() => expect(processIsAlive(descendantPid)).toBe(false), { timeout: 1_000 });
|
|
} finally {
|
|
await stopChild(fixture);
|
|
if (processIsAlive(descendantPid)) {
|
|
process.kill(descendantPid, "SIGKILL");
|
|
}
|
|
}
|
|
});
|
|
});
|