Luigit
repositories / pi-ext

pi-ext

bugabingas pi extensions

owned by admin

extensions/web/__tests__/settings.test.ts

Raw
import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
import { tmpdir } from "node:os";
import path from "node:path";
import { afterEach, describe, expect, it } from "vitest";
import { createTestSession, type TestSession } from "../../../test/harness";
import {
	minimaxApiHost,
	zaiContentSize,
	zaiFreshness,
	zaiLocation,
	zaiMcpUrl,
} from "../settings.ts";
import { zaiSearch } from "../zai.ts";

const extensionPath = path.resolve(import.meta.dirname, "../index.ts");

type Case = {
	user?: unknown;
	project?: unknown;
	env?: Record<string, string>;
	untrusted?: boolean;
};

describe("web settings resolution", () => {
	let t: TestSession | undefined;
	let root: string | undefined;
	afterEach(() => {
		t?.dispose();
		t = undefined;
		if (root) rmSync(root, { recursive: true, force: true });
		root = undefined;
	});

	async function start(input: Case) {
		const dir = mkdtempSync(path.join(tmpdir(), "pi-ext-web-settings-"));
		root = dir;
		const write = (at: string, web: unknown) => {
			mkdirSync(at, { recursive: true });
			writeFileSync(path.join(at, "settings.json"), JSON.stringify({ web }));
		};
		if ("user" in input)
			write(path.join(dir, ".test-home/.pi/agent"), input.user);
		if ("project" in input) write(path.join(dir, ".pi"), input.project);
		const session = await createTestSession({
			cwd: dir,
			extensions: [extensionPath],
			env: {
				ZAI_API_KEY: "test-key",
				ZAI_SEARCH_FRESHNESS: undefined,
				ZAI_SEARCH_MCP_URL: undefined,
				ZAI_SEARCH_BASE_URL: undefined,
				ZAI_SEARCH_CONTENT_SIZE: undefined,
				ZAI_SEARCH_LOCATION: undefined,
				MINIMAX_API_HOST: undefined,
				...input.env,
			},
		});
		t = session;
		if (input.untrusted)
			session.session.settingsManager.setProjectTrusted(false);
		// The harness already started once, before trust was applied.
		const before = session.events.uiCallsFor("notify").length;
		await session.session.extensionRunner.emit({
			type: "session_start",
			reason: "startup",
		});
		const warnings = session.events
			.uiCallsFor("notify")
			.slice(before)
			.filter((call) => String(call.args[0]).startsWith("web: Invalid"))
			.map((call) => call.args[0]);
		const ctx = { cwd: dir, isProjectTrusted: () => !input.untrusted };
		return { ctx, warnings };
	}

	it("uses defaults without settings", async () => {
		const { ctx, warnings } = await start({});
		expect(warnings).toEqual([]);
		expect(zaiFreshness(ctx)).toBeNull();
		expect(zaiMcpUrl(ctx)).toBe(
			"https://api.z.ai/api/mcp/web_search_prime/mcp",
		);
		expect(zaiContentSize(ctx)).toBe("medium");
		expect(zaiLocation(ctx)).toBe("us");
		expect(minimaxApiHost(ctx)).toBe("https://api.minimax.io");
	});

	it("merges trusted project over user settings", async () => {
		const { ctx, warnings } = await start({
			user: {
				zai: { freshness: "week", location: "eu" },
				minimax: { apiHost: "https://user.test" },
			},
			project: { zai: { freshness: "day" } },
		});
		expect(warnings).toEqual([]);
		expect(zaiFreshness(ctx)).toBe("day");
		expect(zaiLocation(ctx)).toBe("eu");
		expect(minimaxApiHost(ctx)).toBe("https://user.test");
	});

	it("prefers env over project and user settings", async () => {
		const { ctx } = await start({
			user: { zai: { contentSize: "high" } },
			project: { zai: { contentSize: "low", mcpUrl: "https://project.test" } },
			env: {
				ZAI_SEARCH_CONTENT_SIZE: "medium",
				MINIMAX_API_HOST: "https://env.test",
			},
		});
		expect(zaiContentSize(ctx)).toBe("medium");
		expect(zaiMcpUrl(ctx)).toBe("https://project.test");
		expect(minimaxApiHost(ctx)).toBe("https://env.test");
	});

	it("orders ZAI_SEARCH_MCP_URL over ZAI_SEARCH_BASE_URL over settings", async () => {
		const both = await start({
			project: { zai: { mcpUrl: "https://project.test" } },
			env: {
				ZAI_SEARCH_MCP_URL: "https://mcp.test",
				ZAI_SEARCH_BASE_URL: "https://base.test",
			},
		});
		expect(zaiMcpUrl(both.ctx)).toBe("https://mcp.test");
		t?.dispose();
		t = undefined;
		const base = await start({
			project: { zai: { mcpUrl: "https://project.test" } },
			env: { ZAI_SEARCH_BASE_URL: "https://base.test" },
		});
		expect(zaiMcpUrl(base.ctx)).toBe("https://base.test");
	});

	it("ignores untrusted project settings", async () => {
		const { ctx, warnings } = await start({
			user: { zai: { freshness: "week" } },
			project: { zai: { freshness: 42 } },
			untrusted: true,
		});
		expect(warnings).toEqual([]);
		expect(zaiFreshness(ctx)).toBe("week");
	});

	it.each([
		[
			"project freshness",
			{
				user: { zai: { freshness: "week" } },
				project: { zai: { freshness: "hour" } },
			},
			"web: Invalid web.zai.freshness from project settings: expected day, week, month, year, or none",
		],
		[
			"env location",
			{ user: { zai: { location: "eu" } }, env: { ZAI_SEARCH_LOCATION: " " } },
			"web: Invalid web.zai.location from environment variable ZAI_SEARCH_LOCATION: expected a non-empty string",
		],
		[
			"user api host",
			{ user: { minimax: { apiHost: 1 } } },
			"web: Invalid web.minimax.apiHost from user settings: expected a non-empty string",
		],
	] satisfies [string, Case, string][])(
		"warns on invalid %s without falling through",
		async (_name, input, warning) => {
			const { warnings } = await start(input);
			expect(warnings).toEqual([warning]);
		},
	);

	it("fails Z.AI search on an invalid setting", async () => {
		await start({ user: { zai: { contentSize: "" } } });
		await expect(zaiSearch("q", undefined)).rejects.toThrow(
			"Invalid web.zai.contentSize from user settings: expected a non-empty string",
		);
	});
});