/* eslint-disable @typescript-eslint/no-explicit-any */ import { describe, expect, it, onTestFinished, vi } from "vitest"; import { approveAll, CopilotClient } from "../src/index.js"; // This file is for unit tests. Where relevant, prefer to add e2e tests in e2e/*.test.ts instead describe("CopilotClient", () => { it("throws when createSession is called without onPermissionRequest", async () => { const client = new CopilotClient(); await client.start(); onTestFinished(() => client.forceStop()); await expect((client as any).createSession({})).rejects.toThrow( /onPermissionRequest.*is required/ ); }); it("throws when resumeSession is called without onPermissionRequest", async () => { const client = new CopilotClient(); await client.start(); onTestFinished(() => client.forceStop()); const session = await client.createSession({ onPermissionRequest: approveAll }); await expect((client as any).resumeSession(session.sessionId, {})).rejects.toThrow( /onPermissionRequest.*is required/ ); }); it("forwards clientName in session.create request", async () => { const client = new CopilotClient(); await client.start(); onTestFinished(() => client.forceStop()); const spy = vi.spyOn((client as any).connection!, "sendRequest"); await client.createSession({ clientName: "my-app", onPermissionRequest: approveAll }); expect(spy).toHaveBeenCalledWith( "session.create", expect.objectContaining({ clientName: "my-app" }) ); }); it("forwards clientName in session.resume request", async () => { const client = new CopilotClient(); await client.start(); onTestFinished(() => client.forceStop()); const session = await client.createSession({ onPermissionRequest: approveAll }); // Mock sendRequest to capture the call without hitting the runtime const spy = vi .spyOn((client as any).connection!, "sendRequest") .mockImplementation(async (method: string, params: any) => { if (method === "session.resume") return { sessionId: params.sessionId }; throw new Error(`Unexpected method: ${method}`); }); await client.resumeSession(session.sessionId, { clientName: "my-app", onPermissionRequest: approveAll, }); expect(spy).toHaveBeenCalledWith( "session.resume", expect.objectContaining({ clientName: "my-app", sessionId: session.sessionId }) ); spy.mockRestore(); }); it("sends session.model.switchTo RPC with correct params", async () => { const client = new CopilotClient(); await client.start(); onTestFinished(() => client.forceStop()); const session = await client.createSession({ onPermissionRequest: approveAll }); // Mock sendRequest to capture the call without hitting the runtime const spy = vi .spyOn((client as any).connection!, "sendRequest") .mockImplementation(async (method: string, _params: any) => { if (method === "session.model.switchTo") return {}; // Fall through for other methods (shouldn't be called) throw new Error(`Unexpected method: ${method}`); }); await session.setModel("gpt-4.1"); expect(spy).toHaveBeenCalledWith("session.model.switchTo", { sessionId: session.sessionId, modelId: "gpt-4.1", }); spy.mockRestore(); }); describe("URL parsing", () => { it("should parse port-only URL format", () => { const client = new CopilotClient({ cliUrl: "8080", logLevel: "error", }); // Verify internal state expect((client as any).actualPort).toBe(8080); expect((client as any).actualHost).toBe("localhost"); expect((client as any).isExternalServer).toBe(true); }); it("should parse host:port URL format", () => { const client = new CopilotClient({ cliUrl: "127.0.0.1:9000", logLevel: "error", }); expect((client as any).actualPort).toBe(9000); expect((client as any).actualHost).toBe("127.0.0.1"); expect((client as any).isExternalServer).toBe(true); }); it("should parse http://host:port URL format", () => { const client = new CopilotClient({ cliUrl: "http://localhost:7000", logLevel: "error", }); expect((client as any).actualPort).toBe(7000); expect((client as any).actualHost).toBe("localhost"); expect((client as any).isExternalServer).toBe(true); }); it("should parse https://host:port URL format", () => { const client = new CopilotClient({ cliUrl: "https://example.com:443", logLevel: "error", }); expect((client as any).actualPort).toBe(443); expect((client as any).actualHost).toBe("example.com"); expect((client as any).isExternalServer).toBe(true); }); it("should throw error for invalid URL format", () => { expect(() => { new CopilotClient({ cliUrl: "invalid-url", logLevel: "error", }); }).toThrow(/Invalid cliUrl format/); }); it("should throw error for invalid port - too high", () => { expect(() => { new CopilotClient({ cliUrl: "localhost:99999", logLevel: "error", }); }).toThrow(/Invalid port in cliUrl/); }); it("should throw error for invalid port - zero", () => { expect(() => { new CopilotClient({ cliUrl: "localhost:0", logLevel: "error", }); }).toThrow(/Invalid port in cliUrl/); }); it("should throw error for invalid port - negative", () => { expect(() => { new CopilotClient({ cliUrl: "localhost:-1", logLevel: "error", }); }).toThrow(/Invalid port in cliUrl/); }); it("should throw error when cliUrl is used with useStdio", () => { expect(() => { new CopilotClient({ cliUrl: "localhost:8080", useStdio: true, logLevel: "error", }); }).toThrow(/cliUrl is mutually exclusive/); }); it("should throw error when cliUrl is used with cliPath", () => { expect(() => { new CopilotClient({ cliUrl: "localhost:8080", cliPath: "/path/to/cli", logLevel: "error", }); }).toThrow(/cliUrl is mutually exclusive/); }); it("should set useStdio to false when cliUrl is provided", () => { const client = new CopilotClient({ cliUrl: "8080", logLevel: "error", }); expect(client["options"].useStdio).toBe(false); }); it("should mark client as using external server", () => { const client = new CopilotClient({ cliUrl: "localhost:8080", logLevel: "error", }); expect((client as any).isExternalServer).toBe(true); }); }); describe("Auth options", () => { it("should accept githubToken option", () => { const client = new CopilotClient({ githubToken: "gho_test_token", logLevel: "error", }); expect((client as any).options.githubToken).toBe("gho_test_token"); }); it("should default useLoggedInUser to true when no githubToken", () => { const client = new CopilotClient({ logLevel: "error", }); expect((client as any).options.useLoggedInUser).toBe(true); }); it("should default useLoggedInUser to false when githubToken is provided", () => { const client = new CopilotClient({ githubToken: "gho_test_token", logLevel: "error", }); expect((client as any).options.useLoggedInUser).toBe(false); }); it("should allow explicit useLoggedInUser: true with githubToken", () => { const client = new CopilotClient({ githubToken: "gho_test_token", useLoggedInUser: true, logLevel: "error", }); expect((client as any).options.useLoggedInUser).toBe(true); }); it("should allow explicit useLoggedInUser: false without githubToken", () => { const client = new CopilotClient({ useLoggedInUser: false, logLevel: "error", }); expect((client as any).options.useLoggedInUser).toBe(false); }); it("should throw error when githubToken is used with cliUrl", () => { expect(() => { new CopilotClient({ cliUrl: "localhost:8080", githubToken: "gho_test_token", logLevel: "error", }); }).toThrow(/githubToken and useLoggedInUser cannot be used with cliUrl/); }); it("should throw error when useLoggedInUser is used with cliUrl", () => { expect(() => { new CopilotClient({ cliUrl: "localhost:8080", useLoggedInUser: false, logLevel: "error", }); }).toThrow(/githubToken and useLoggedInUser cannot be used with cliUrl/); }); }); describe("overridesBuiltInTool in tool definitions", () => { it("sends overridesBuiltInTool in tool definition on session.create", async () => { const client = new CopilotClient(); await client.start(); onTestFinished(() => client.forceStop()); const spy = vi.spyOn((client as any).connection!, "sendRequest"); await client.createSession({ onPermissionRequest: approveAll, tools: [ { name: "grep", description: "custom grep", handler: async () => "ok", overridesBuiltInTool: true, }, ], }); const payload = spy.mock.calls.find((c) => c[0] === "session.create")![1] as any; expect(payload.tools).toEqual([ expect.objectContaining({ name: "grep", overridesBuiltInTool: true }), ]); }); it("sends overridesBuiltInTool in tool definition on session.resume", async () => { const client = new CopilotClient(); await client.start(); onTestFinished(() => client.forceStop()); const session = await client.createSession({ onPermissionRequest: approveAll }); // Mock sendRequest to capture the call without hitting the runtime const spy = vi .spyOn((client as any).connection!, "sendRequest") .mockImplementation(async (method: string, params: any) => { if (method === "session.resume") return { sessionId: params.sessionId }; throw new Error(`Unexpected method: ${method}`); }); await client.resumeSession(session.sessionId, { onPermissionRequest: approveAll, tools: [ { name: "grep", description: "custom grep", handler: async () => "ok", overridesBuiltInTool: true, }, ], }); const payload = spy.mock.calls.find((c) => c[0] === "session.resume")![1] as any; expect(payload.tools).toEqual([ expect.objectContaining({ name: "grep", overridesBuiltInTool: true }), ]); spy.mockRestore(); }); }); });