test(mcp): fix three MCP boundary suites on native Windows (#2312)

* test(mcp): fix three MCP boundary suites on native Windows

* test(mcp): report unavailable root symlink coverage as a skip

---------

Co-authored-by: morluto <76467478+morluto@users.noreply.github.com>
This commit is contained in:
pablodiazjorgeandmorluto authored and GitHub committed 2026-10-13 03:13:35 +08:00
1 parent d1aaeee400
commit a97b713f74
3 files changed
+67 -55

No files matched your search

+53 -48
View File
@@ -247,7 +247,7 @@ it("traces a standalone Mach-O file whose name ends in .app", async () => {
});
});
it.skipIf(process.getuid?.() === 0)(
it.skipIf(process.platform === "win32" || process.getuid?.() === 0)(
"keeps a permission denied while canonicalizing the opened bundle",
async () => {
const parent = await createTestTempDirectory("rea-dylib-mcp-revoked-");
@@ -451,52 +451,57 @@ it.each([
},
);
it("reports caller-selected roots that cannot be traced as invalid input", async () => {
const app = join(
await createTestTempDirectory("rea-dylib-roots-"),
"Fixture.app",
);
await mkdir(join(app, "Contents/MacOS"), { recursive: true });
await writeFile(
join(app, "Contents/Info.plist"),
"<plist><dict><key>CFBundleExecutable</key><string>App</string></dict></plist>",
);
await writeFile(
join(app, "Contents/MacOS/App"),
machoImage({ commands: [buildVersionCommand(1)] }),
);
await symlink("/bin/ls", join(app, "Contents/MacOS/outside"));
await withClient(async (client) => {
const opened = await client.callTool({
name: "open_binary",
arguments: { path: app },
});
expect(opened.isError, JSON.stringify(opened)).not.toBe(true);
for (const [root, message] of [
["Contents/MacOS/Missing", "is not a regular file"],
["Contents/MacOS", "is not a regular file"],
["Contents/Info.plist", "is not a Mach-O image"],
["Contents/MacOS/outside", "resolves outside the analyzed root"],
] as const) {
const called = await client.callTool({
name: "trace_dylib_resolution",
arguments: { roots: ["Contents/MacOS/App", root] },
});
expect(called.isError, root).toBe(true);
expect(parseMcpToolError(called), root).toMatchObject({
error: {
code: "invalid_request",
details: {
issues: [
{
path: ["roots", 1],
reason: "invalid_value",
message: expect.stringContaining(`Root ${root} ${message}`),
},
],
for (const [root, message] of [
["Contents/MacOS/Missing", "is not a regular file"],
["Contents/MacOS", "is not a regular file"],
["Contents/Info.plist", "is not a Mach-O image"],
["Contents/MacOS/outside", "resolves outside the analyzed root"],
] as const) {
const needsFileSymlink = root === "Contents/MacOS/outside";
// File symlinks on Windows need Developer Mode or an elevated token.
it.skipIf(process.platform === "win32" && needsFileSymlink)(
`reports caller-selected root ${root} as invalid input`,
async () => {
const app = join(
await createTestTempDirectory("rea-dylib-roots-"),
"Fixture.app",
);
await mkdir(join(app, "Contents/MacOS"), { recursive: true });
await writeFile(
join(app, "Contents/Info.plist"),
"<plist><dict><key>CFBundleExecutable</key><string>App</string></dict></plist>",
);
await writeFile(
join(app, "Contents/MacOS/App"),
machoImage({ commands: [buildVersionCommand(1)] }),
);
if (needsFileSymlink) await symlink("/bin/ls", join(app, root));
await withClient(async (client) => {
const opened = await client.callTool({
name: "open_binary",
arguments: { path: app },
});
expect(opened.isError, JSON.stringify(opened)).not.toBe(true);
const called = await client.callTool({
name: "trace_dylib_resolution",
arguments: { roots: ["Contents/MacOS/App", root] },
});
expect(called.isError, root).toBe(true);
expect(parseMcpToolError(called), root).toMatchObject({
error: {
code: "invalid_request",
details: {
issues: [
{
path: ["roots", 1],
reason: "invalid_value",
message: expect.stringContaining(`Root ${root} ${message}`),
},
],
},
},
},
});
});
}
});
});
},
);
}
+11 -6
View File
@@ -1,7 +1,7 @@
import { parseEvidence } from "../../../src/domain/evidence.js";
import { ok as resultOk } from "../../../src/domain/result.js";
import { rm, symlink, writeFile } from "node:fs/promises";
import { join } from "node:path";
import { basename, join } from "node:path";
import { pathToFileURL } from "node:url";
import { Client, InMemoryTransport } from "@modelcontextprotocol/client";
@@ -248,12 +248,12 @@ it("exposes endpoint-scoped Electron discovery and inspection as Evidence", asyn
expect(inspected.isError).not.toBe(true);
expect(inspected.structuredContent).toMatchObject({
normalized_result: {
target: { file_path: expect.stringMatching(/\/index\.html$/u) },
target: { file_path: expect.stringMatching(/[\\/]index\.html$/u) },
scripts: {
items: [
expect.objectContaining({
frame_id: "frame-main",
file_path: expect.stringMatching(/\/app\.js$/u),
file_path: expect.stringMatching(/[\\/]app\.js$/u),
cdp_hash: null,
length: null,
is_module: null,
@@ -264,7 +264,7 @@ it("exposes endpoint-scoped Electron discovery and inspection as Evidence", asyn
expect.objectContaining({
target_id: "electron-worker",
opener_target_id: "electron-page",
file_path: expect.stringMatching(/\/worker\.js$/u),
file_path: expect.stringMatching(/[\\/]worker\.js$/u),
}),
],
},
@@ -276,8 +276,13 @@ it("runs active Electron scenarios with selected paths and inferred working dire
temporary.push(root);
const applicationPath = join(root, "main.js");
await writeFile(applicationPath, "module.exports = {};\n");
const aliasedRoot = join(root, "..", `${root.split("/").at(-1)}-alias`);
await symlink(root, aliasedRoot, "dir");
const aliasedRoot = join(root, "..", `${basename(root)}-alias`);
// A junction needs no symlink privilege on Windows.
await symlink(
root,
aliasedRoot,
process.platform === "win32" ? "junction" : "dir",
);
temporary.push(aliasedRoot);
const workingDirectory = await createTestTempDirectory(
"rea-electron-working-directory-",
@@ -33,7 +33,9 @@ it("delivers typed tool failures without violating the advertised success schema
const diagnostic = parseMcpToolError(reply);
expect(diagnostic.error.message, request.name).not.toBe("");
if ("path" in request.arguments || "input_path" in request.arguments)
expect(JSON.stringify(diagnostic), request.name).toContain(missing);
expect(JSON.stringify(diagnostic), request.name).toContain(
JSON.stringify(missing).slice(1, -1),
);
if (reply.structuredContent !== undefined) {
const validate = new Ajv2020({
strict: false,