mirror of
https://github.com/morluto/rea.git
synced 2026-11-11 01:13:39 +00:00
Node force-terminates Windows children for SIGINT as well as SIGTERM. Keep POSIX shutdown assertions and leave Windows console interruption explicitly unverified.
53 lines
2.1 KiB
TypeScript
53 lines
2.1 KiB
TypeScript
import { once } from "node:events";
|
|
import { describe, expect } from "vitest";
|
|
|
|
import { MCP_SHUTDOWN_FAILED } from "../../../src/main/messages.js";
|
|
import { rawHttpRequest } from "../../support/http/rawRequest.js";
|
|
import { startMcpHttpProcess } from "../../support/mcp/streamableHttpProcess.js";
|
|
import { processTest } from "../../support/process/processFixture.js";
|
|
|
|
const SHUTDOWN_TIMEOUT_MS = 90_000;
|
|
|
|
const shutdownSignals: readonly (readonly [NodeJS.Signals, number])[] = [
|
|
["SIGTERM", 143],
|
|
["SIGINT", 130],
|
|
];
|
|
|
|
// Windows child.kill() force-terminates even for SIGINT; it does not deliver a console interrupt.
|
|
describe.skipIf(process.platform === "win32")("rea mcp --http shutdown", () => {
|
|
for (const [signal, status] of shutdownSignals)
|
|
processTest(
|
|
`releases the listener and exits ${String(status)} on ${signal}`,
|
|
async ({ processes }) => {
|
|
const started = await startMcpHttpProcess(processes);
|
|
// The live transport refuses a GET that does not accept an event stream.
|
|
const before = await rawHttpRequest(started.url);
|
|
expect(before.status).toBe(406);
|
|
started.child.kill(signal);
|
|
const [code, signalCode] = await once(started.child, "close");
|
|
expect(code).toBe(status);
|
|
expect(signalCode).toBeNull();
|
|
expect(started.stderr()).not.toContain(MCP_SHUTDOWN_FAILED);
|
|
await expect(rawHttpRequest(started.url)).rejects.toMatchObject({
|
|
code: "ECONNREFUSED",
|
|
});
|
|
},
|
|
SHUTDOWN_TIMEOUT_MS,
|
|
);
|
|
|
|
processTest(
|
|
"exits with the signal status when the signal lands as startup completes",
|
|
async ({ processes }) => {
|
|
const started = await startMcpHttpProcess(processes);
|
|
// No request first: the signal arrives at the edge between startup and the lifecycle owner.
|
|
const [signal, status] = shutdownSignals[0]!;
|
|
started.child.kill(signal);
|
|
const [code, signalCode] = await once(started.child, "close");
|
|
expect(code).toBe(status);
|
|
expect(signalCode).toBeNull();
|
|
expect(started.stderr()).not.toContain(MCP_SHUTDOWN_FAILED);
|
|
},
|
|
SHUTDOWN_TIMEOUT_MS,
|
|
);
|
|
});
|