repositories / pi-ext
pi-ext
bugabingas pi extensions
owned by admin
extensions/intellij/__tests__/unit.test.ts
Rawimport { 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<string, unknown>; 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<string> {
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();
});
});