import { chmod, mkdir, mkdtemp, rm, writeFile } from "node:fs/promises"; import { tmpdir } from "node:os"; import { join } from "node:path"; import { afterEach, describe, expect, it } from "vitest"; import { acceptsProjectPath, findIdeaLauncher, normalizeIntellijUrl, schemaWithoutInjectedProjectPath, toolCallTimeout, validateRemoteTool, } from "../implementation"; describe("IntelliJ URL validation", () => { it.each([ ["http://localhost:63342/mcp", "http://localhost:63342/mcp"], ["http://127.0.0.2:63342/mcp", "http://127.0.0.2:63342/mcp"], ["http://[::1]:63342/mcp", "http://[::1]:63342/mcp"], ])("accepts loopback HTTP %s", (input, expected) => { expect(normalizeIntellijUrl(input)).toBe(expected); }); it.each([ "https://localhost:63342/mcp", "http://example.com/mcp", "http://user:secret@localhost/mcp", "http://localhost/mcp#fragment", "not a url", ])("rejects unsafe endpoint %s", (url) => { expect(() => normalizeIntellijUrl(url)).toThrow(); }); }); describe("IntelliJ catalog validation", () => { it("preserves required fields and detects projectPath", () => { const tool = validateRemoteTool({ name: "read.file", inputSchema: { type: "object", properties: { projectPath: { type: "string" }, path: { type: "string" }, }, required: ["path"], }, }); expect(tool.inputSchema.required).toEqual(["path"]); expect(acceptsProjectPath(tool.inputSchema)).toBe(true); }); it("detects projectPath through local references and composition", () => { expect( acceptsProjectPath({ type: "object", allOf: [{ $ref: "#/$defs/project" }], $defs: { project: { type: "object", properties: { projectPath: { type: "string" } }, }, }, }), ).toBe(true); }); it("hides the bridge-injected projectPath from composed agent schemas", () => { const schema = { type: "object" as const, allOf: [{ $ref: "#/$defs/project" }], $defs: { project: { type: "object", properties: { projectPath: { type: "string" }, path: { type: "string" }, }, required: ["projectPath", "path"], }, }, }; const agentSchema = schemaWithoutInjectedProjectPath(schema); const project = agentSchema.$defs as { project: { properties: Record; required: string[] }; }; expect(project.project.properties).not.toHaveProperty("projectPath"); expect(project.project.required).toEqual(["path"]); expect(schema.$defs.project.properties).toHaveProperty("projectPath"); }); it("allows IntelliJ's requested operation timeout plus transport grace", () => { expect(toolCallTimeout({})).toBe(65_000); expect(toolCallTimeout({ timeout: 120_000 })).toBe(125_000); expect(toolCallTimeout({ timeout: -1 })).toBe(65_000); }); it.each([ { name: "bad name", inputSchema: { type: "object" } }, { name: "ok", inputSchema: { type: "string" } }, { name: "ok", inputSchema: { type: "object", properties: {}, required: ["missing"], }, }, { name: "ok", inputSchema: { type: "object", properties: { malformed: null }, }, }, ])("rejects malformed tools", (tool) => { expect(() => validateRemoteTool(tool)).toThrow(); }); }); describe("IntelliJ launcher lookup", () => { const roots: string[] = []; afterEach(async () => { await Promise.all( roots.splice(0).map((root) => rm(root, { recursive: true })), ); }); async function root(): Promise { const value = await mkdtemp(join(tmpdir(), "intellij-launcher-")); roots.push(value); return value; } it("finds executable Unix launchers through PATH", async () => { const bin = await root(); const executable = join(bin, "idea"); await writeFile(executable, "#!/bin/sh\n"); await chmod(executable, 0o700); expect(await findIdeaLauncher({ PATH: bin }, "linux")).toEqual({ command: executable, argsBeforePath: [], }); }); it("uses the explicit command host for Windows script launchers", async () => { const bin = await root(); const script = join(bin, "idea.cmd"); await writeFile(script, "@echo off\n"); expect( await findIdeaLauncher( { Path: bin, ComSpec: "C:\\Windows\\System32\\cmd.exe" }, "win32", ), ).toEqual({ command: "C:\\Windows\\System32\\cmd.exe", argsBeforePath: ["/d", "/v:off", "/s", "/c"], script, }); }); it("returns absent when macOS PATH has no launcher", async () => { const bin = await root(); await mkdir(join(bin, "idea")); expect(await findIdeaLauncher({ PATH: bin }, "darwin")).toBeUndefined(); }); });