Files
reverse-engineering-anything/tests/boundary/mcp/javascriptTimerDelay.test.ts
T
morluto 71712f27f7 test(mcp): open stdio clients through the shared local-tools fixture (#10411)
* test(mcp): open stdio clients through the shared local-tools fixture

Twelve files built their own StdioClientTransport and Client for
`rea mcp`. Those clients never listed tools, so the SDK did not
validate structured results against advertised output schemas, and each
file repeated transport setup, stderr draining and close ordering.

Use connectLocalToolsMcp, which lists tools, validates replies,
silences logging, leaves deep providers unconfigured and owns cleanup.
Per-test clients register test-scoped cleanup; module-level clients
collect cleanup for afterAll.

* test(mcp): keep failed file-scoped MCP cleanup owned

The three module-level clients removed every cleanup callback before
running them, so a failed cleanup lost its only handle. Add
fileLocalToolsMcp, which connects before the file's tests and removes
each cleanup only after it succeeds, and use it in those files.
2026-11-14 11:47:55 +08:00

95 lines
3.6 KiB
TypeScript

import { createHash } from "node:crypto";
import { readFile, writeFile } from "node:fs/promises";
import { join } from "node:path";
import { expect } from "vitest";
import { toolContract } from "../../../src/contracts/toolContracts.js";
import { javascriptApplicationAnalysisResultSchema } from "../../../src/domain/javascript/javascriptApplicationAnalysis.js";
import { connectLocalToolsMcp } from "../../fixtures/localToolsMcp.js";
import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js";
import { cliTest } from "../../support/cli/cliFixture.js";
cliTest(
"retains unknown timer delays and unrelated facts through public CLI and MCP",
async ({ cli }) => {
const root = await createTestTempDirectory("rea-timer-delay-");
const source = [
'export function greet() { return "hello"; }',
"setTimeout(() => greet(), 1e999);",
"setInterval(() => greet(), 1e309);",
"setTimeout(() => greet(), 100);",
"setTimeout(() => greet(), 1e308);",
"setTimeout(() => greet(), Infinity);",
"setImmediate(() => greet());",
].join("\n");
const path = join(root, "main.js");
await writeFile(path, source);
const digest = createHash("sha256").update(source).digest("hex");
const cliResult = await cli.run({
arguments: ["analyze-javascript-application", root, "--json"],
});
expect(cliResult.exitCode).toBe(0);
const contract = toolContract("analyze_javascript_application");
const cliEvidence = contract.outputSchema.parse(cliResult.json);
const graph = javascriptApplicationAnalysisResultSchema.parse(
cliEvidence.normalized_result,
).semantic_graph;
const timers = graph.nodes.filter(({ kind }) => kind === "timer");
const pairs = timers.map(({ properties }) => [
properties.method,
properties.delay_milliseconds,
]);
const sorted = (values: unknown[]) =>
values.map((value) => JSON.stringify(value)).sort();
expect(sorted(pairs)).toEqual(
sorted([
["setTimeout", null],
["setInterval", null],
["setTimeout", 100],
["setTimeout", 1e308],
["setTimeout", null],
["setImmediate", null],
]),
);
expect(
graph.nodes.find(
({ kind, label }) => kind === "function" && label === "greet",
)?.identity,
).toMatchObject({ module_path: "main.js", artifact_sha256: digest });
for (const timer of timers) {
expect(Object.hasOwn(timer.properties, "delay_milliseconds")).toBe(true);
expect(timer.identity).toMatchObject({
module_path: "main.js",
artifact_sha256: digest,
});
}
const { client } = await connectLocalToolsMcp();
const reply = await client.callTool({
name: contract.name,
arguments: { input_path: root },
});
expect(reply.isError).not.toBe(true);
const evidence = contract.outputSchema.parse(reply.structuredContent);
expect(evidence.evidence_id).toBe(cliEvidence.evidence_id);
const text = reply.content.find((item) => item.type === "text");
if (text?.type !== "text") throw new Error("Missing inline Evidence");
expect(contract.outputSchema.parse(JSON.parse(text.text)).evidence_id).toBe(
evidence.evidence_id,
);
const retained = javascriptApplicationAnalysisResultSchema.parse(
evidence.normalized_result,
).semantic_graph;
expect(retained.nodes.filter(({ kind }) => kind === "timer")).toEqual(
timers,
);
expect(
retained.nodes.some(
({ kind, label }) => kind === "function" && label === "greet",
),
).toBe(true);
await expect(client.ping()).resolves.toEqual({});
expect(await readFile(path, "utf8")).toBe(source);
},
);