@agentrq/acp-gateway 0.2.2 → 0.2.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,342 @@
1
+ import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
2
+ import { execFileSync } from "node:child_process";
3
+ import { mkdtemp, readFile, rm, stat, writeFile } from "node:fs/promises";
4
+ import { existsSync } from "node:fs";
5
+ import { tmpdir } from "node:os";
6
+ import * as path from "node:path";
7
+ import { archiveKind, assertChecksum, assertVerifiable, defaultCacheDir, downloadArchive, extractArchive, installBinaryAgent, installDir, moveInto, resolveAgentLaunch, runExtractionTool, resolveExecutable, sha256, } from "../agentInstall.js";
8
+ const agent = {
9
+ id: "demo-acp",
10
+ name: "Demo Agent",
11
+ version: "1.2.3",
12
+ description: "A demo agent",
13
+ distribution: {},
14
+ };
15
+ /** Builds a real .tar.gz containing an executable, so extraction is exercised for real. */
16
+ async function makeTarball(cmdName, contents) {
17
+ const staging = await mkdtemp(path.join(tmpdir(), "acp-gateway-fixture-"));
18
+ try {
19
+ await writeFile(path.join(staging, cmdName), contents);
20
+ const archive = path.join(staging, "out.tar.gz");
21
+ execFileSync("tar", ["-czf", archive, "-C", staging, cmdName]);
22
+ return new Uint8Array(await readFile(archive));
23
+ }
24
+ finally {
25
+ await rm(staging, { recursive: true, force: true });
26
+ }
27
+ }
28
+ function okResponse(data) {
29
+ return {
30
+ ok: true,
31
+ status: 200,
32
+ statusText: "OK",
33
+ arrayBuffer: async () => data.buffer.slice(data.byteOffset, data.byteOffset + data.byteLength),
34
+ };
35
+ }
36
+ describe("agentInstall", () => {
37
+ let cacheDir;
38
+ let errorSpy;
39
+ beforeEach(async () => {
40
+ cacheDir = await mkdtemp(path.join(tmpdir(), "acp-gateway-cache-"));
41
+ errorSpy = vi.spyOn(console, "error").mockImplementation(() => { });
42
+ });
43
+ afterEach(async () => {
44
+ errorSpy.mockRestore();
45
+ await rm(cacheDir, { recursive: true, force: true });
46
+ });
47
+ describe("defaultCacheDir", () => {
48
+ it("uses XDG_CACHE_HOME where it is set", () => {
49
+ expect(defaultCacheDir("linux", { XDG_CACHE_HOME: "/xdg" })).toBe(path.join("/xdg", "acp-gateway", "agents"));
50
+ });
51
+ it("falls back to the per-user cache on macOS and Linux", () => {
52
+ expect(defaultCacheDir("darwin", {})).toMatch(/\.cache[\\/]acp-gateway[\\/]agents$/);
53
+ });
54
+ it("uses LOCALAPPDATA on Windows", () => {
55
+ expect(defaultCacheDir("win32", { LOCALAPPDATA: "C:\\Users\\me\\AppData\\Local" })).toBe(path.join("C:\\Users\\me\\AppData\\Local", "acp-gateway", "agents"));
56
+ expect(defaultCacheDir("win32", {})).toMatch(/AppData[\\/]Local[\\/]acp-gateway/);
57
+ });
58
+ });
59
+ describe("archiveKind", () => {
60
+ it("recognises every format the registry allows", () => {
61
+ expect(archiveKind("https://x/y.zip")).toBe("zip");
62
+ expect(archiveKind("https://x/y.tar.gz")).toBe("tar.gz");
63
+ expect(archiveKind("https://x/y.tgz")).toBe("tar.gz");
64
+ expect(archiveKind("https://x/y.tar.bz2")).toBe("tar.bz2");
65
+ expect(archiveKind("https://x/y.tbz2")).toBe("tar.bz2");
66
+ });
67
+ it("ignores query strings and fragments", () => {
68
+ expect(archiveKind("https://x/y.ZIP?token=abc#frag")).toBe("zip");
69
+ });
70
+ it("treats anything else as a raw executable", () => {
71
+ expect(archiveKind("https://x/agent-darwin-arm64")).toBe("raw");
72
+ });
73
+ });
74
+ describe("installDir", () => {
75
+ it("keeps each version and platform apart", () => {
76
+ expect(installDir("/cache", "demo", "darwin-aarch64", "1.2.3")).toBe(path.join("/cache", "demo@1.2.3", "darwin-aarch64"));
77
+ });
78
+ });
79
+ describe("resolveExecutable", () => {
80
+ it("resolves the registry's cmd inside the install directory", () => {
81
+ expect(resolveExecutable("/cache/demo", "./bin/agent")).toBe(path.resolve("/cache/demo/bin/agent"));
82
+ });
83
+ it("refuses a cmd that climbs out of the install directory", () => {
84
+ expect(() => resolveExecutable("/cache/demo", "../../../usr/bin/curl")).toThrow(/points outside its install directory/);
85
+ });
86
+ });
87
+ describe("assertVerifiable", () => {
88
+ const unverifiable = { archive: "https://x/y.zip", cmd: "./demo" };
89
+ it("refuses an archive the registry publishes no checksum for", () => {
90
+ expect(() => assertVerifiable(agent, unverifiable, false)).toThrow(/publishes no sha256 for "demo-acp".*--allow-unverified-agent/s);
91
+ });
92
+ it("allows it once the user has explicitly opted in", () => {
93
+ expect(() => assertVerifiable(agent, unverifiable, true)).not.toThrow();
94
+ });
95
+ it("allows an archive that does publish a checksum", () => {
96
+ expect(() => assertVerifiable(agent, { ...unverifiable, sha256: "a".repeat(64) }, false)).not.toThrow();
97
+ });
98
+ });
99
+ describe("assertChecksum", () => {
100
+ const data = new Uint8Array([1, 2, 3]);
101
+ it("accepts a download that matches, whatever the case", () => {
102
+ const digest = sha256(data);
103
+ expect(() => assertChecksum(agent, { archive: "https://x/y", cmd: "./d", sha256: digest.toUpperCase() }, data)).not.toThrow();
104
+ });
105
+ it("refuses a download that does not match", () => {
106
+ expect(() => assertChecksum(agent, { archive: "https://x/y", cmd: "./d", sha256: "b".repeat(64) }, data)).toThrow(/Checksum mismatch for "demo-acp"/);
107
+ });
108
+ it("has nothing to check when the registry publishes no checksum", () => {
109
+ expect(() => assertChecksum(agent, { archive: "https://x/y", cmd: "./d" }, data)).not.toThrow();
110
+ });
111
+ });
112
+ describe("downloadArchive", () => {
113
+ it("returns the bytes", async () => {
114
+ const data = new Uint8Array([9, 8, 7]);
115
+ const fetchImpl = vi.fn().mockResolvedValue(okResponse(data));
116
+ expect(await downloadArchive("https://x/y.zip", fetchImpl)).toEqual(data);
117
+ });
118
+ it("reports a failed download", async () => {
119
+ const fetchImpl = vi
120
+ .fn()
121
+ .mockResolvedValue({ ok: false, status: 403, statusText: "Forbidden" });
122
+ await expect(downloadArchive("https://x/y.zip", fetchImpl)).rejects.toThrow(/Failed to download https:\/\/x\/y.zip: 403 Forbidden/);
123
+ });
124
+ });
125
+ describe("extractArchive", () => {
126
+ it("writes a raw download straight out as the executable", async () => {
127
+ const dir = path.join(cacheDir, "raw");
128
+ await extractArchive(new Uint8Array([1, 2]), "raw", dir, "./demo-agent");
129
+ expect(await readFile(path.join(dir, "demo-agent"))).toEqual(Buffer.from([1, 2]));
130
+ });
131
+ it("unpacks a real tarball and removes the archive afterwards", async () => {
132
+ const tarball = await makeTarball("demo", "#!/bin/sh\necho hi\n");
133
+ const dir = path.join(cacheDir, "tar");
134
+ await extractArchive(tarball, "tar.gz", dir, "./demo");
135
+ expect(await readFile(path.join(dir, "demo"), "utf8")).toContain("echo hi");
136
+ expect(existsSync(path.join(dir, "archive.tar.gz"))).toBe(false);
137
+ });
138
+ it("uses unzip for zips on Linux, where tar cannot read them", async () => {
139
+ const dir = path.join(cacheDir, "zip");
140
+ // `unzip` is not guaranteed on every machine, so assert on the failure
141
+ // message rather than on a successful extraction.
142
+ await expect(extractArchive(new Uint8Array([1]), "zip", dir, "./demo", "linux")).rejects.toThrow(/unzip/);
143
+ });
144
+ it("reports an extraction tool that is not installed", async () => {
145
+ await expect(runExtractionTool("acp-gateway-no-such-tool", [], cacheDir)).rejects.toThrow(/Could not run "acp-gateway-no-such-tool" to unpack the agent archive/);
146
+ });
147
+ it("reports a corrupt archive with the tool's own diagnostics", async () => {
148
+ const dir = path.join(cacheDir, "bad");
149
+ await expect(extractArchive(new Uint8Array([1, 2, 3]), "tar.gz", dir, "./demo", "darwin")).rejects.toThrow(/failed to unpack the agent archive/);
150
+ });
151
+ });
152
+ describe("moveInto", () => {
153
+ it("copies when the staged install cannot simply be renamed into place", async () => {
154
+ const from = await mkdtemp(path.join(tmpdir(), "acp-gateway-move-"));
155
+ await writeFile(path.join(from, "demo"), "binary");
156
+ // A destination whose parents do not exist: rename() fails, and the copy
157
+ // fallback — the same path taken when the temp dir is another mount —
158
+ // has to create the tree.
159
+ const to = path.join(cacheDir, "deep", "deeper", "install");
160
+ await moveInto(from, to);
161
+ expect(await readFile(path.join(to, "demo"), "utf8")).toBe("binary");
162
+ await rm(from, { recursive: true, force: true });
163
+ });
164
+ });
165
+ describe("installBinaryAgent", () => {
166
+ async function install(overrides = {}) {
167
+ const tarball = await makeTarball("demo", "#!/bin/sh\nexit 0\n");
168
+ const target = {
169
+ archive: "https://example.com/demo.tar.gz",
170
+ sha256: sha256(tarball),
171
+ cmd: "./demo",
172
+ args: ["--acp"],
173
+ ...overrides.target,
174
+ };
175
+ const fetchImpl = vi.fn().mockResolvedValue(okResponse(tarball));
176
+ const spec = await installBinaryAgent({
177
+ agent,
178
+ target,
179
+ platformTarget: "darwin-aarch64",
180
+ cacheDir,
181
+ fetchImpl: fetchImpl,
182
+ platform: "darwin",
183
+ ...overrides.options,
184
+ });
185
+ return { spec, fetchImpl, tarball };
186
+ }
187
+ it("downloads, verifies, unpacks and makes the agent executable", async () => {
188
+ const { spec } = await install();
189
+ expect(spec).toEqual({
190
+ command: path.join(cacheDir, "demo-acp@1.2.3", "darwin-aarch64", "demo"),
191
+ args: ["--acp"],
192
+ env: undefined,
193
+ kind: "binary",
194
+ });
195
+ expect(existsSync(spec.command)).toBe(true);
196
+ // 0o111 — executable by someone.
197
+ expect((await stat(spec.command)).mode & 0o111).toBeGreaterThan(0);
198
+ });
199
+ it("skips the executable bit on Windows, which has none", async () => {
200
+ const { spec } = await install({ options: { platform: "win32" } });
201
+ expect(existsSync(spec.command)).toBe(true);
202
+ });
203
+ it("reuses a cached install instead of downloading again", async () => {
204
+ await install();
205
+ const { fetchImpl } = await install();
206
+ expect(fetchImpl).not.toHaveBeenCalled();
207
+ expect(errorSpy.mock.calls.flat().join("\n")).toContain("Using cached demo-acp 1.2.3");
208
+ });
209
+ it("refuses an archive with no published checksum", async () => {
210
+ await expect(install({ target: { sha256: undefined } })).rejects.toThrow(/publishes no sha256/);
211
+ });
212
+ it("installs an unverified archive when explicitly allowed, and says so", async () => {
213
+ const { spec } = await install({
214
+ target: { sha256: undefined },
215
+ options: { allowUnverified: true },
216
+ });
217
+ expect(existsSync(spec.command)).toBe(true);
218
+ expect(errorSpy.mock.calls.flat().join("\n")).toContain("publishes no checksum");
219
+ });
220
+ it("refuses a download that does not match the checksum", async () => {
221
+ await expect(install({ target: { sha256: "c".repeat(64) } })).rejects.toThrow(/Checksum mismatch/);
222
+ });
223
+ it("leaves nothing cached when the archive lacks the promised command", async () => {
224
+ await expect(install({ target: { cmd: "./not-in-archive" } })).rejects.toThrow(/does not contain "\.\/not-in-archive"/);
225
+ expect(existsSync(installDir(cacheDir, agent.id, "darwin-aarch64", agent.version))).toBe(false);
226
+ });
227
+ });
228
+ describe("resolveAgentLaunch", () => {
229
+ const gemini = {
230
+ id: "gemini",
231
+ name: "Gemini CLI",
232
+ version: "0.58.0",
233
+ description: "Google's CLI",
234
+ distribution: { npx: { package: "@google/gemini-cli@0.58.0", args: ["--acp"] } },
235
+ };
236
+ const pyAgent = {
237
+ id: "fast-agent",
238
+ name: "fast-agent",
239
+ version: "1.0.0",
240
+ description: "A Python agent",
241
+ distribution: { uvx: { package: "fast-agent-mcp" } },
242
+ };
243
+ const binaryOnly = {
244
+ ...agent,
245
+ distribution: {
246
+ binary: {
247
+ "linux-x86_64": { archive: "https://x/y.tar.gz", sha256: "a".repeat(64), cmd: "./demo" },
248
+ },
249
+ },
250
+ };
251
+ const registry = {
252
+ version: "1.0.0",
253
+ agents: [gemini, pyAgent, binaryOnly],
254
+ };
255
+ it("runs npm-distributed agents through npx without downloading anything", async () => {
256
+ const fetchImpl = vi.fn();
257
+ const spec = await resolveAgentLaunch({
258
+ id: "gemini",
259
+ registry,
260
+ platformTarget: "darwin-aarch64",
261
+ fetchImpl: fetchImpl,
262
+ });
263
+ expect(spec).toEqual({
264
+ command: "npx",
265
+ args: ["-y", "@google/gemini-cli@0.58.0", "--acp"],
266
+ env: undefined,
267
+ kind: "npx",
268
+ });
269
+ expect(fetchImpl).not.toHaveBeenCalled();
270
+ });
271
+ it("names npx explicitly on Windows, where a bare npx cannot be spawned", async () => {
272
+ const spec = await resolveAgentLaunch({
273
+ id: "gemini",
274
+ registry,
275
+ platformTarget: "windows-x86_64",
276
+ platform: "win32",
277
+ });
278
+ expect(spec.command).toBe("npx.cmd");
279
+ });
280
+ it("runs PyPI-distributed agents through uvx", async () => {
281
+ const spec = await resolveAgentLaunch({
282
+ id: "fast-agent",
283
+ registry,
284
+ platformTarget: "linux-x86_64",
285
+ });
286
+ expect(spec).toEqual({
287
+ command: "uvx",
288
+ args: ["fast-agent-mcp"],
289
+ env: undefined,
290
+ kind: "uvx",
291
+ });
292
+ });
293
+ it("installs the binary when that is the only distribution", async () => {
294
+ const tarball = await makeTarball("demo", "#!/bin/sh\nexit 0\n");
295
+ const registryWithDigest = {
296
+ version: "1.0.0",
297
+ agents: [
298
+ {
299
+ ...binaryOnly,
300
+ distribution: {
301
+ binary: {
302
+ "linux-x86_64": {
303
+ archive: "https://x/y.tar.gz",
304
+ sha256: sha256(tarball),
305
+ cmd: "./demo",
306
+ },
307
+ },
308
+ },
309
+ },
310
+ ],
311
+ };
312
+ const fetchImpl = vi.fn().mockResolvedValue(okResponse(tarball));
313
+ const spec = await resolveAgentLaunch({
314
+ id: "demo-acp",
315
+ registry: registryWithDigest,
316
+ platformTarget: "linux-x86_64",
317
+ cacheDir,
318
+ fetchImpl: fetchImpl,
319
+ platform: "linux",
320
+ });
321
+ expect(spec.kind).toBe("binary");
322
+ expect(existsSync(spec.command)).toBe(true);
323
+ });
324
+ it("reports an id the registry does not list", async () => {
325
+ await expect(resolveAgentLaunch({ id: "nope", registry, platformTarget: "linux-x86_64" })).rejects.toThrow(/No agent "nope" in the ACP registry/);
326
+ });
327
+ it("reports an agent that has no build for this machine", async () => {
328
+ await expect(resolveAgentLaunch({ id: "demo-acp", registry, platformTarget: "windows-aarch64" })).rejects.toThrow(/publishes no build of "demo-acp" for windows-aarch64 \(it publishes: binary\)/);
329
+ });
330
+ it("says so when an agent publishes no distribution at all", async () => {
331
+ const empty = {
332
+ version: "1.0.0",
333
+ agents: [{ ...agent, distribution: {} }],
334
+ };
335
+ await expect(resolveAgentLaunch({ id: "demo-acp", registry: empty, platformTarget: "linux-x86_64" })).rejects.toThrow(/it publishes: nothing/);
336
+ });
337
+ it("reports an unsupported platform by name", async () => {
338
+ await expect(resolveAgentLaunch({ id: "demo-acp", registry, platformTarget: undefined })).rejects.toThrow(/publishes no build of "demo-acp" for/);
339
+ });
340
+ });
341
+ });
342
+ //# sourceMappingURL=agentInstall.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"agentInstall.test.js","sourceRoot":"","sources":["../../src/__tests__/agentInstall.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,283 @@
1
+ import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
2
+ import { EventEmitter } from "node:events";
3
+ import { spawn } from "node:child_process";
4
+ import { createInterface } from "node:readline/promises";
5
+ import { AUTH_REQUIRED_CODE, authMethodType, describeAuthMethods, isAuthRequiredError, login, logout, pickAuthMethod, promptForAuthMethod, runAuthMethod, runTerminalAuth, supportsLogout, } from "../auth.js";
6
+ vi.mock("node:child_process", () => ({ spawn: vi.fn() }));
7
+ vi.mock("node:readline/promises", () => ({ createInterface: vi.fn() }));
8
+ const spawnMock = vi.mocked(spawn);
9
+ const createInterfaceMock = vi.mocked(createInterface);
10
+ const agentMethod = {
11
+ id: "agent-login",
12
+ name: "Agent login",
13
+ description: "Sign in through the agent",
14
+ };
15
+ const terminalMethod = {
16
+ type: "terminal",
17
+ id: "cli-login",
18
+ name: "CLI login",
19
+ args: ["auth", "login"],
20
+ env: { AUTH_MODE: "interactive" },
21
+ };
22
+ const launch = { command: "gemini", args: ["--acp"], env: { FROM_CONFIG: "1" } };
23
+ /** An `auth_required` refusal as the SDK surfaces it to the caller. */
24
+ function authRequiredError() {
25
+ return Object.assign(new Error("Authentication required: run login first"), {
26
+ code: AUTH_REQUIRED_CODE,
27
+ });
28
+ }
29
+ describe("auth", () => {
30
+ let errorSpy;
31
+ let logSpy;
32
+ beforeEach(() => {
33
+ vi.clearAllMocks();
34
+ errorSpy = vi.spyOn(console, "error").mockImplementation(() => { });
35
+ logSpy = vi.spyOn(console, "log").mockImplementation(() => { });
36
+ });
37
+ afterEach(() => {
38
+ errorSpy.mockRestore();
39
+ logSpy.mockRestore();
40
+ });
41
+ describe("authMethodType", () => {
42
+ it("treats a missing type as the agent-driven default", () => {
43
+ expect(authMethodType(agentMethod)).toBe("agent");
44
+ expect(authMethodType({ ...agentMethod, type: "agent" })).toBe("agent");
45
+ });
46
+ it("recognises terminal methods", () => {
47
+ expect(authMethodType(terminalMethod)).toBe("terminal");
48
+ });
49
+ });
50
+ describe("describeAuthMethods", () => {
51
+ it("says so when the agent needs no login", () => {
52
+ expect(describeAuthMethods([])).toMatch(/no authentication methods/);
53
+ expect(describeAuthMethods(undefined)).toMatch(/no authentication methods/);
54
+ expect(describeAuthMethods(null)).toMatch(/no authentication methods/);
55
+ });
56
+ it("numbers the methods and marks terminal ones", () => {
57
+ const text = describeAuthMethods([agentMethod, terminalMethod]);
58
+ expect(text).toContain("1. Agent login (agent-login) — Sign in through the agent");
59
+ expect(text).toContain("2. CLI login (cli-login) [terminal login]");
60
+ });
61
+ });
62
+ describe("isAuthRequiredError", () => {
63
+ it("recognises the protocol's auth_required refusal", () => {
64
+ expect(isAuthRequiredError(authRequiredError())).toBe(true);
65
+ });
66
+ it("reads the error out of a nested JSON-RPC envelope", () => {
67
+ expect(isAuthRequiredError({
68
+ error: { code: AUTH_REQUIRED_CODE, message: "auth_required" },
69
+ })).toBe(true);
70
+ });
71
+ it("reads the top level when `error` is not an envelope", () => {
72
+ expect(isAuthRequiredError({
73
+ error: "auth_required",
74
+ code: AUTH_REQUIRED_CODE,
75
+ message: "Authentication required",
76
+ })).toBe(true);
77
+ });
78
+ it("ignores other failures that share the -32000 code", () => {
79
+ expect(isAuthRequiredError(Object.assign(new Error("permission failed"), { code: AUTH_REQUIRED_CODE }))).toBe(false);
80
+ });
81
+ it("ignores errors with a different code or no shape at all", () => {
82
+ expect(isAuthRequiredError(Object.assign(new Error("Authentication required"), { code: -32603 }))).toBe(false);
83
+ expect(isAuthRequiredError({ code: AUTH_REQUIRED_CODE })).toBe(false);
84
+ expect(isAuthRequiredError(new Error("Authentication required"))).toBe(false);
85
+ expect(isAuthRequiredError("nope")).toBe(false);
86
+ expect(isAuthRequiredError(null)).toBe(false);
87
+ });
88
+ });
89
+ describe("pickAuthMethod", () => {
90
+ it("returns nothing when the agent advertises no methods", () => {
91
+ expect(pickAuthMethod([])).toBeUndefined();
92
+ expect(pickAuthMethod(undefined)).toBeUndefined();
93
+ });
94
+ it("honours an explicitly named method, terminal included", () => {
95
+ expect(pickAuthMethod([agentMethod, terminalMethod], { preferredId: "cli-login" })).toBe(terminalMethod);
96
+ });
97
+ it("returns nothing when the named method is not advertised", () => {
98
+ expect(pickAuthMethod([agentMethod], { preferredId: "missing" })).toBeUndefined();
99
+ });
100
+ it("prefers agent-driven methods, which need no human", () => {
101
+ expect(pickAuthMethod([terminalMethod, agentMethod])).toBe(agentMethod);
102
+ });
103
+ it("falls back to a terminal method only when a terminal is available", () => {
104
+ expect(pickAuthMethod([terminalMethod])).toBeUndefined();
105
+ expect(pickAuthMethod([terminalMethod], { allowTerminal: true })).toBe(terminalMethod);
106
+ });
107
+ });
108
+ describe("promptForAuthMethod", () => {
109
+ it("returns nothing when there is nothing to choose from", async () => {
110
+ expect(await promptForAuthMethod([], vi.fn())).toBeUndefined();
111
+ });
112
+ it("does not ask when the agent offers a single method", async () => {
113
+ const ask = vi.fn();
114
+ expect(await promptForAuthMethod([agentMethod], ask)).toBe(agentMethod);
115
+ expect(ask).not.toHaveBeenCalled();
116
+ });
117
+ it("takes the first method when the user just hits enter", async () => {
118
+ const ask = vi.fn().mockResolvedValue(" ");
119
+ expect(await promptForAuthMethod([agentMethod, terminalMethod], ask)).toBe(agentMethod);
120
+ });
121
+ it("accepts a selection by number", async () => {
122
+ const ask = vi.fn().mockResolvedValue("2");
123
+ expect(await promptForAuthMethod([agentMethod, terminalMethod], ask)).toBe(terminalMethod);
124
+ });
125
+ it("accepts a selection by method id", async () => {
126
+ const ask = vi.fn().mockResolvedValue("cli-login");
127
+ expect(await promptForAuthMethod([agentMethod, terminalMethod], ask)).toBe(terminalMethod);
128
+ });
129
+ it("re-asks after an answer that matches nothing", async () => {
130
+ const ask = vi.fn().mockResolvedValueOnce("99").mockResolvedValueOnce("1");
131
+ expect(await promptForAuthMethod([agentMethod, terminalMethod], ask)).toBe(agentMethod);
132
+ expect(ask).toHaveBeenCalledTimes(2);
133
+ expect(errorSpy).toHaveBeenCalledWith('[auth] "99" is not one of the listed methods.');
134
+ });
135
+ it("gives up after three unusable answers", async () => {
136
+ const ask = vi.fn().mockResolvedValue("nonsense");
137
+ expect(await promptForAuthMethod([agentMethod, terminalMethod], ask)).toBeUndefined();
138
+ expect(ask).toHaveBeenCalledTimes(3);
139
+ });
140
+ it("asks on the terminal when no asker is supplied", async () => {
141
+ const close = vi.fn();
142
+ createInterfaceMock.mockReturnValue({
143
+ question: vi.fn().mockResolvedValue("2"),
144
+ close,
145
+ });
146
+ expect(await promptForAuthMethod([agentMethod, terminalMethod])).toBe(terminalMethod);
147
+ expect(createInterfaceMock).toHaveBeenCalledWith({
148
+ input: process.stdin,
149
+ output: process.stderr,
150
+ });
151
+ expect(close).toHaveBeenCalled();
152
+ });
153
+ });
154
+ describe("runTerminalAuth", () => {
155
+ it("re-runs the agent invocation with the method's args and env", async () => {
156
+ const child = new EventEmitter();
157
+ spawnMock.mockReturnValue(child);
158
+ const pending = runTerminalAuth(terminalMethod, launch);
159
+ child.emit("exit", 0, null);
160
+ await expect(pending).resolves.toBeUndefined();
161
+ expect(spawnMock).toHaveBeenCalledWith("gemini", ["--acp", "auth", "login"], expect.objectContaining({
162
+ stdio: "inherit",
163
+ env: expect.objectContaining({ FROM_CONFIG: "1", AUTH_MODE: "interactive" }),
164
+ }));
165
+ });
166
+ it("passes no extra args or env when the method declares none", async () => {
167
+ const child = new EventEmitter();
168
+ spawnMock.mockReturnValue(child);
169
+ const pending = runTerminalAuth({ ...agentMethod, type: "terminal" }, {
170
+ command: "gemini",
171
+ args: ["--acp"],
172
+ });
173
+ child.emit("exit", 0, null);
174
+ await pending;
175
+ expect(spawnMock).toHaveBeenCalledWith("gemini", ["--acp"], expect.anything());
176
+ });
177
+ it("fails when the login process exits non-zero", async () => {
178
+ const child = new EventEmitter();
179
+ spawnMock.mockReturnValue(child);
180
+ const pending = runTerminalAuth(terminalMethod, launch);
181
+ child.emit("exit", 1, null);
182
+ await expect(pending).rejects.toThrow(/Terminal login "cli-login" failed \(code=1/);
183
+ });
184
+ it("fails when the login process cannot start", async () => {
185
+ const child = new EventEmitter();
186
+ spawnMock.mockReturnValue(child);
187
+ const pending = runTerminalAuth(terminalMethod, launch);
188
+ child.emit("error", new Error("ENOENT"));
189
+ await expect(pending).rejects.toThrow(/failed to start: ENOENT/);
190
+ });
191
+ });
192
+ describe("runAuthMethod", () => {
193
+ it("asks the agent to authenticate for agent-driven methods", async () => {
194
+ const connection = { authenticate: vi.fn().mockResolvedValue({}), logout: vi.fn() };
195
+ await runAuthMethod(connection, agentMethod, launch);
196
+ expect(connection.authenticate).toHaveBeenCalledWith({ methodId: "agent-login" });
197
+ });
198
+ it("never sends a terminal method to authenticate", async () => {
199
+ const child = new EventEmitter();
200
+ spawnMock.mockReturnValue(child);
201
+ const connection = { authenticate: vi.fn(), logout: vi.fn() };
202
+ const pending = runAuthMethod(connection, terminalMethod, launch);
203
+ child.emit("exit", 0, null);
204
+ await pending;
205
+ expect(connection.authenticate).not.toHaveBeenCalled();
206
+ expect(spawnMock).toHaveBeenCalled();
207
+ });
208
+ });
209
+ describe("login", () => {
210
+ const connection = () => ({ authenticate: vi.fn().mockResolvedValue({}), logout: vi.fn() });
211
+ it("does nothing when the agent advertises no login", async () => {
212
+ const conn = connection();
213
+ expect(await login({ connection: conn, methods: [], launch })).toBeUndefined();
214
+ expect(conn.authenticate).not.toHaveBeenCalled();
215
+ });
216
+ it("logs in with the method the user named", async () => {
217
+ const conn = connection();
218
+ const used = await login({
219
+ connection: conn,
220
+ methods: [agentMethod, { ...agentMethod, id: "other", name: "Other" }],
221
+ launch,
222
+ preferredId: "other",
223
+ });
224
+ expect(used?.id).toBe("other");
225
+ expect(conn.authenticate).toHaveBeenCalledWith({ methodId: "other" });
226
+ });
227
+ it("rejects a method the agent does not advertise", async () => {
228
+ await expect(login({ connection: connection(), methods: [agentMethod], launch, preferredId: "nope" })).rejects.toThrow(/Unknown authentication method "nope"/);
229
+ });
230
+ it("picks an agent-driven method when running unattended", async () => {
231
+ const conn = connection();
232
+ const used = await login({ connection: conn, methods: [terminalMethod, agentMethod], launch });
233
+ expect(used).toBe(agentMethod);
234
+ });
235
+ it("asks the user which method to use when a terminal is available", async () => {
236
+ const conn = connection();
237
+ const ask = vi.fn().mockResolvedValue("1");
238
+ const used = await login({
239
+ connection: conn,
240
+ methods: [agentMethod, terminalMethod],
241
+ launch,
242
+ interactive: true,
243
+ ask,
244
+ });
245
+ expect(used).toBe(agentMethod);
246
+ expect(ask).toHaveBeenCalled();
247
+ });
248
+ it("fails when only a terminal login is offered and no terminal is available", async () => {
249
+ await expect(login({ connection: connection(), methods: [terminalMethod], launch })).rejects.toThrow(/No usable authentication method/);
250
+ });
251
+ it("fails when the user does not choose a method", async () => {
252
+ await expect(login({
253
+ connection: connection(),
254
+ methods: [agentMethod, terminalMethod],
255
+ launch,
256
+ interactive: true,
257
+ ask: vi.fn().mockResolvedValue("nonsense"),
258
+ })).rejects.toThrow(/No usable authentication method/);
259
+ });
260
+ });
261
+ describe("supportsLogout", () => {
262
+ it("is true only when the agent advertises the capability", () => {
263
+ expect(supportsLogout({ auth: { logout: {} } })).toBe(true);
264
+ expect(supportsLogout({ auth: { logout: null } })).toBe(false);
265
+ expect(supportsLogout({ auth: {} })).toBe(false);
266
+ expect(supportsLogout({})).toBe(false);
267
+ expect(supportsLogout(undefined)).toBe(false);
268
+ });
269
+ });
270
+ describe("logout", () => {
271
+ it("skips agents that do not implement logout", async () => {
272
+ const conn = { authenticate: vi.fn(), logout: vi.fn() };
273
+ expect(await logout(conn, {})).toBe(false);
274
+ expect(conn.logout).not.toHaveBeenCalled();
275
+ });
276
+ it("ends the authenticated state when supported", async () => {
277
+ const conn = { authenticate: vi.fn(), logout: vi.fn().mockResolvedValue({}) };
278
+ expect(await logout(conn, { auth: { logout: {} } })).toBe(true);
279
+ expect(conn.logout).toHaveBeenCalledWith({});
280
+ });
281
+ });
282
+ });
283
+ //# sourceMappingURL=auth.test.js.map