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