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:
1 parent
d1aaeee400
commit
a97b713f74
3 files changed
+67
-55
No files matched your search
@@ -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}`),
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
},
|
||||
);
|
||||
}
|
||||
@@ -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,
|
||||
|
||||
Reference in new issue
Block a user