Luigit
repositories / pi-ext

pi-ext

bugabingas pi extensions

owned by admin

extensions/intellij/__tests__/settings.test.ts

Raw
import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { fileURLToPath } from "node:url";
import { afterEach, describe, expect, it, vi } from "vitest";
import {
	createPiSession,
	type PiSession,
} from "../../../test/fixture/create-pi-session.ts";
import {
	findIdeaProjectRoot,
	INTELLIJ_URL_SETTING,
	normalizeIntellijUrl,
} from "../index.ts";
import { resolveSetting } from "../src/pi-ext-settings.ts";

const extensionPath = fileURLToPath(new URL("../index.ts", import.meta.url));
let root: string | undefined;
let session: PiSession | undefined;

function notify(): ReturnType<typeof vi.spyOn> {
	const runner = session!.session.extensionRunner;
	const ui = { ...runner.getUIContext() };
	const spy = vi.spyOn(ui, "notify");
	runner.setUIContext(ui, "print");
	return spy;
}

function project(): string {
	root = mkdtempSync(join(tmpdir(), "pi-ext-intellij-settings-"));
	mkdirSync(join(root, ".idea"));
	return root;
}

afterEach(async () => {
	await session?.dispose();
	session = undefined;
	if (root) rmSync(root, { recursive: true, force: true });
	root = undefined;
});

describe("intellij settings and detection", () => {
	it("accepts only credential-free loopback HTTP URLs", () => {
		expect(normalizeIntellijUrl("http://127.0.0.1:63342/mcp")).toBe(
			"http://127.0.0.1:63342/mcp",
		);
		for (const value of [
			"https://localhost/mcp",
			"http://example.com/mcp",
			"http://user:secret@localhost/mcp",
			"not a url",
		])
			expect(() => normalizeIntellijUrl(value)).toThrow();
	});

	it("uses trusted project setting and ignores untrusted project setting", async () => {
		const cwd = project();
		mkdirSync(join(cwd, ".pi"));
		writeFileSync(
			join(cwd, ".pi", "settings.json"),
			JSON.stringify({ intellij: { url: "http://127.0.0.1:63342/project" } }),
		);
		session = await createPiSession({
			cwd,
			userSettings: { intellij: { url: "http://127.0.0.1:63342/user" } },
			extensionPaths: [extensionPath],
		});
		const pi = { getFlag: () => undefined };
		expect(
			resolveSetting(
				pi,
				{ cwd, isProjectTrusted: () => true },
				INTELLIJ_URL_SETTING,
			),
		).toMatchObject({
			value: "http://127.0.0.1:63342/project",
			source: "project",
		});
		expect(
			resolveSetting(
				pi,
				{ cwd, isProjectTrusted: () => false },
				INTELLIJ_URL_SETTING,
			),
		).toMatchObject({ value: "http://127.0.0.1:63342/user", source: "user" });
	});

	it("warns only for IntelliJ projects with missing or invalid URL", async () => {
		const cwd = project();
		session = await createPiSession({ cwd, extensionPaths: [extensionPath] });
		const missing = notify();
		await vi.waitFor(() =>
			expect(missing).toHaveBeenCalledWith(
				expect.stringContaining("Set intellij.url"),
				"warning",
			),
		);

		await session.dispose();
		session = undefined;
		rmSync(cwd, { recursive: true, force: true });
		root = undefined;
		const invalid = project();
		session = await createPiSession({
			cwd: invalid,
			userSettings: { intellij: { url: "http://user:secret@localhost/mcp" } },
			extensionPaths: [extensionPath],
		});
		const bad = notify();
		await vi.waitFor(() =>
			expect(bad).toHaveBeenCalledWith(
				expect.stringContaining("must not contain credentials"),
				"warning",
			),
		);
	});

	it("finds nearest parent root and has no activation without .idea", async () => {
		const cwd = mkdtempSync(join(tmpdir(), "pi-ext-intellij-no-project-"));
		root = cwd;
		mkdirSync(join(cwd, "a", "b"), { recursive: true });
		expect(await findIdeaProjectRoot(join(cwd, "a", "b"))).toBeUndefined();
		session = await createPiSession({
			cwd,
			userSettings: { intellij: { url: "http://127.0.0.1:63342/mcp" } },
			extensionPaths: [extensionPath],
		});
		await new Promise((resolve) => setTimeout(resolve, 20));
		expect(session.errors).toEqual([]);
	});
});