Luigit
repositories / pi-ext

pi-ext

bugabingas pi extensions

owned by admin

extensions/intellij/__tests__/unit.test.ts

Raw
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<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();
	});
});