Repository navigation
Expand file tree
/
Copy pathserver.test.ts
More file actions
438 lines (399 loc) · 22.7 KB
/
Copy pathserver.test.ts
File metadata and controls
438 lines (399 loc) · 22.7 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
import { createFakePluginHost } from "@get-bb/plugin-sdk/testing";
import { describe, expect, it, vi } from "vitest";
import type { GhProcess, GhResult, GhRunner } from "./gh.js";
import { ACCOUNT_POOL_ID, CHANGED, createTeamSetupPlugin, DEVBOX_ID, LINEAR_ID, LOGIN_TTL_MS, SEEDED_NOTE } from "./server.js";
// A gh whose `auth login` the test drives: print lines, then exit.
function fakeGh(runs: Record<string, GhResult> = {}) {
const started: Array<{ args: string[]; emit: (line: string) => void; exit: (result: Partial<GhResult>) => void; killed: boolean }> = [];
const runner: GhRunner = {
run: vi.fn(async (args: string[]) => runs[args.join(" ")] ?? { exitCode: 1, stdout: "", stderr: "not stubbed: gh " + args.join(" ") }),
start: vi.fn((args: string[]): GhProcess => {
const listeners: Array<(line: string) => void> = [];
let resolveExit!: (r: GhResult) => void;
const exited = new Promise<GhResult>((resolve) => {
resolveExit = resolve;
});
const entry = {
args,
emit: (line: string) => listeners.forEach((l) => l(line)),
exit: (result: Partial<GhResult>) => resolveExit({ exitCode: 0, stdout: "", stderr: "", ...result }),
killed: false,
};
started.push(entry);
return {
onLine: (l) => {
listeners.push(l);
},
exited,
kill: () => {
entry.killed = true;
entry.exit({ exitCode: 1, stderr: "killed" });
},
};
}),
};
return { runner, started };
}
const PROMPT = ["! First copy your one-time code: AB12-CD34", "Open this URL to continue in your web browser: https://github.com/login/device"];
interface World {
plugins?: Array<{ id: string; enabled: boolean }>;
builtInGit?: { status: string; statusMessage: string };
pool?: unknown;
devbox?: unknown;
linear?: unknown;
hosts?: Array<{ id?: string; name: string; status?: string }>;
figmaHost?: { claude: boolean; configured: boolean; authenticated: boolean; pending: boolean; url: string | null; message: string | null };
figmaComplete?: { ok: boolean; message: string | null };
runs?: Record<string, GhResult>;
settings?: Record<string, string>;
env?: Record<string, string | undefined>;
setVariable?: (input: { name: string; value: string; note: string | null }) => Promise<unknown>;
}
const FIGMA_OPTIONAL = { claude: true, configured: false, authenticated: false, pending: false, url: null, message: null };
async function setup(world: World = {}) {
const gh = fakeGh(world.runs);
const hostCalls: Array<{ method: string; input: unknown; hostId: string }> = [];
const { bb, harness } = createFakePluginHost({
pluginId: "team-setup",
settings: world.settings ?? {},
experimental_callHostRpc: (call) => {
hostCalls.push({ method: call.method, input: call.input, hostId: call.hostId });
if (call.method === "state") return world.figmaHost ?? FIGMA_OPTIONAL;
if (call.method === "start") return { url: "https://www.figma.com/oauth/mcp?response_type=code", port: 53280 };
if (call.method === "complete") return world.figmaComplete ?? { ok: true, message: null };
if (call.method === "cancel") return { ok: true };
throw new Error(`no answer for host ${call.method}`);
},
});
harness.sdk.stub("plugins.list", async () => ({
plugins: (world.plugins ?? [{ id: ACCOUNT_POOL_ID, enabled: true }, { id: DEVBOX_ID, enabled: true }, { id: LINEAR_ID, enabled: true }]).map((p) => ({ ...p, name: p.id })),
}));
harness.sdk.stub("system.machineEnvironment", async () => ({
builtInGit: world.builtInGit ?? { status: "not logged in", statusMessage: "gh is not logged in on the server" },
variables: [],
}));
harness.sdk.stub("hosts.list", async () => (world.hosts ?? []).map((host, index) => ({ id: host.id ?? `host-${index}`, name: host.name, status: host.status ?? "connected" })));
const setVariables: Array<{ name: string; value: string; note: string | null }> = [];
harness.sdk.stub("system.setMachineEnvironmentVariable", async (input: { name: string; value: string; note: string | null }) => {
setVariables.push(input);
if (world.setVariable) await world.setVariable(input);
return { builtInGit: { status: "not logged in", statusMessage: "" }, variables: [{ name: input.name, note: input.note, secret: true, value: null }] };
});
const rpcCalls: Array<{ pluginId: string; method: string; input: unknown }> = [];
harness.sdk.stub("plugins.callRpc", async (args: { pluginId: string; method: string; input: unknown; outputSchema: { parse: (v: unknown) => unknown } }) => {
rpcCalls.push({ pluginId: args.pluginId, method: args.method, input: args.input });
const key = `${args.pluginId}:${args.method}`;
const answers: Record<string, unknown> = {
[`${ACCOUNT_POOL_ID}:status.get`]: world.pool ?? { routing: { claude: true, codex: true }, accounts: [] },
[`${ACCOUNT_POOL_ID}:login.start`]: { sessionId: "00000000-0000-4000-8000-000000000001", authorizeUrl: "https://claude.ai/oauth/authorize?x" },
[`${ACCOUNT_POOL_ID}:login.complete`]: { label: "me@example.com", provider: "claude" },
[`${ACCOUNT_POOL_ID}:codexLogin.start`]: {
sessionId: "00000000-0000-4000-8000-000000000002",
verificationUri: "https://auth.openai.com/device",
userCode: "WXYZ-1234",
expiresAt: 1_700_000_000_000,
intervalMs: 5000,
},
[`${ACCOUNT_POOL_ID}:codexLogin.poll`]: { status: "complete", account: { label: "codex@example.com" } },
[`${ACCOUNT_POOL_ID}:codexLogin.cancel`]: { cancelled: true },
[`${DEVBOX_ID}:status`]: world.devbox ?? { connected: false, project: null },
[`${DEVBOX_ID}:connect`]: { url: "https://devbox.example/connect/authorize?state=s" },
[`${LINEAR_ID}:status`]: world.linear ?? { connected: false, configured: true, user: null, organization: null },
[`${LINEAR_ID}:connect`]: { url: "https://linear.example/oauth/authorize?state=s" },
};
if (!(key in answers)) throw new Error(`no answer for ${key}`);
return args.outputSchema.parse(answers[key]);
});
let now = 1_000_000;
await createTeamSetupPlugin({ gh: gh.runner, now: () => now, env: world.env ?? {} })(bb);
const status = async () => (await harness.callRpc("status", null)) as Awaited<ReturnType<typeof statusType>>;
return { bb, harness, gh, rpcCalls, hostCalls, status, setVariables, advance: (ms: number) => (now += ms) };
}
// Only for the inferred return type above.
declare function statusType(): Promise<{
ai: { available: boolean; message: string | null; accounts: Array<{ label: string }> };
github: { status: string; login: string | null; orgMember: boolean | null; pending: { code: string; url: string } | null; lastError: string | null };
devbox: { available: boolean; connected: boolean; project: string | null; message: string | null };
linear: { available: boolean; connected: boolean; configured: boolean; user: string | null; organization: string | null; message: string | null };
figma: { available: boolean; message: string | null; machine: string | null; claude: boolean; configured: boolean; authenticated: boolean; pending: boolean; url: string | null };
machines: { names: string[] };
steps: { ai: string; github: string; devbox: string; linear: string; figma: string; machine: string };
complete: boolean;
}>;
const signedIn = {
"api --hostname github.com user --jq .login": { exitCode: 0, stdout: "octocat\n", stderr: "" },
"api --hostname github.com user/memberships/orgs/bitcomplete --jq .state": { exitCode: 0, stdout: "active\n", stderr: "" },
};
describe("status", () => {
it("starts with everything to do", async () => {
const { status } = await setup();
const s = await status();
expect(s.steps).toEqual({ ai: "todo", github: "todo", devbox: "todo", linear: "optional", figma: "unavailable", machine: "todo" });
expect(s.complete).toBe(false);
expect(s.github.login).toBeNull();
});
it("is complete when every step is done", async () => {
const { status, gh } = await setup({
builtInGit: { status: "logged in", statusMessage: "ok" },
pool: { routing: { claude: true, codex: false }, accounts: [{ id: "a", provider: "claude", label: "me", enabled: true, status: "ready" }] },
devbox: { connected: true, project: "dylan" },
linear: { connected: true, configured: true, user: { id: "u1", name: "jane", email: null }, organization: { name: "Acme", urlKey: "acme" } },
hosts: [{ name: "box" }],
runs: signedIn,
});
const s = await status();
expect(s.steps).toEqual({ ai: "done", github: "done", devbox: "done", linear: "done", figma: "optional", machine: "done" });
expect(s.complete).toBe(true);
expect(s.linear).toMatchObject({ connected: true, user: "jane", organization: "Acme" });
expect(s.github.login).toBe("octocat");
expect(s.github.orgMember).toBe(true);
// Identity is cached between status calls.
await status();
expect(gh.runner.run).toHaveBeenCalledTimes(2);
});
it("does not count an account whose provider routing is off", async () => {
const { status } = await setup({
pool: { routing: { claude: false, codex: true }, accounts: [{ id: "a", provider: "claude", label: "me", enabled: true, status: "ready" }] },
});
expect((await status()).steps.ai).toBe("todo");
});
it("marks a step unavailable when its plugin is off, and that does not block completion", async () => {
const { status } = await setup({
plugins: [{ id: ACCOUNT_POOL_ID, enabled: true }, { id: DEVBOX_ID, enabled: false }],
builtInGit: { status: "overridden", statusMessage: "" },
pool: { routing: { claude: true, codex: true }, accounts: [{ id: "a", provider: "codex", label: "me", enabled: true, status: "ready" }] },
hosts: [{ name: "laptop" }],
});
const s = await status();
expect(s.devbox.available).toBe(false);
expect(s.devbox.message).toContain("turned off");
expect(s.steps).toEqual({ ai: "done", github: "done", devbox: "unavailable", linear: "unavailable", figma: "optional", machine: "done" });
expect(s.complete).toBe(true);
});
it("treats Linear as optional: not connecting it does not block completion", async () => {
const { status } = await setup({
builtInGit: { status: "logged in", statusMessage: "ok" },
pool: { routing: { claude: true, codex: false }, accounts: [{ id: "a", provider: "claude", label: "me", enabled: true, status: "ready" }] },
devbox: { connected: true, project: "dylan" },
hosts: [{ name: "box" }],
runs: signedIn,
});
const s = await status();
expect(s.steps.linear).toBe("optional");
expect(s.linear).toMatchObject({ available: true, configured: true, connected: false });
expect(s.complete).toBe(true);
});
it("does not ask the developer to connect Linear while the server has no OAuth client ID", async () => {
const { status } = await setup({ linear: { connected: false, configured: false, user: null, organization: null } });
const s = await status();
expect(s.steps.linear).toBe("unavailable");
expect(s.linear).toMatchObject({ available: true, configured: false });
});
it("warns about an account outside the organization", async () => {
const { status } = await setup({
builtInGit: { status: "logged in", statusMessage: "ok" },
runs: { ...signedIn, "api --hostname github.com user/memberships/orgs/bitcomplete --jq .state": { exitCode: 1, stdout: "", stderr: "404" } },
});
expect((await status()).github.orgMember).toBe(false);
});
it("skips the organization check when the setting is empty", async () => {
const { status, gh } = await setup({ builtInGit: { status: "logged in", statusMessage: "ok" }, runs: signedIn, settings: { githubOrg: "" } });
expect((await status()).github.orgMember).toBeNull();
expect(gh.runner.run).toHaveBeenCalledTimes(1);
});
});
describe("GitHub sign-in", () => {
it("starts gh headless and hands back the code it prints", async () => {
const { harness, gh, status } = await setup();
const pending = harness.callRpc("githubSignIn", null);
await vi.waitFor(() => expect(gh.started).toHaveLength(1));
expect(gh.started[0].args).toEqual(["auth", "login", "--hostname", "github.com", "--git-protocol", "https", "--web", "--insecure-storage"]);
for (const line of PROMPT) gh.started[0].emit(line);
expect(await pending).toEqual({ code: "AB12-CD34", url: "https://github.com/login/device", expiresAt: 1_000_000 + LOGIN_TTL_MS });
expect((await status()).github.pending).toEqual({ code: "AB12-CD34", url: "https://github.com/login/device", expiresAt: 1_000_000 + LOGIN_TTL_MS });
// A second click returns the same code rather than a second gh.
expect(await harness.callRpc("githubSignIn", null)).toMatchObject({ code: "AB12-CD34" });
expect(gh.started).toHaveLength(1);
gh.started[0].emit("✓ Logged in as octocat");
gh.started[0].exit({ exitCode: 0 });
await vi.waitFor(() => expect(harness.realtimeSignals.map((s) => s.channel)).toContain(CHANGED));
const s = await status();
expect(s.github.pending).toBeNull();
expect(s.github.lastError).toBeNull();
});
it("reports why gh gave up", async () => {
const { harness, gh, status } = await setup();
const pending = harness.callRpc("githubSignIn", null);
await vi.waitFor(() => expect(gh.started).toHaveLength(1));
for (const line of PROMPT) gh.started[0].emit(line);
await pending;
gh.started[0].exit({ exitCode: 1, stderr: "error: The device code has expired\n" });
await vi.waitFor(async () => expect((await status()).github.lastError).toBe("error: The device code has expired"));
expect((await status()).github.pending).toBeNull();
});
it("fails the call when gh exits before printing a code", async () => {
const { harness, gh } = await setup();
const pending = harness.callRpc("githubSignIn", null);
await vi.waitFor(() => expect(gh.started).toHaveLength(1));
gh.started[0].exit({ exitCode: 1, stderr: "The value of the GH_TOKEN environment variable is being used for authentication.\n" });
await expect(pending).rejects.toThrow(/GH_TOKEN environment variable/u);
});
it("cancel kills the waiting gh", async () => {
const { harness, gh, status } = await setup();
const pending = harness.callRpc("githubSignIn", null);
await vi.waitFor(() => expect(gh.started).toHaveLength(1));
for (const line of PROMPT) gh.started[0].emit(line);
await pending;
await harness.callRpc("githubCancel", null);
expect(gh.started[0].killed).toBe(true);
const s = await status();
expect(s.github.pending).toBeNull();
expect(s.github.lastError).toBeNull();
});
it("sign out runs gh auth logout and forgets the identity", async () => {
const { harness, gh, status } = await setup({
builtInGit: { status: "logged in", statusMessage: "ok" },
runs: { ...signedIn, "auth logout --hostname github.com": { exitCode: 0, stdout: "", stderr: "" } },
});
expect((await status()).github.login).toBe("octocat");
await harness.callRpc("githubSignOut", null);
expect(gh.runner.run).toHaveBeenCalledWith(["auth", "logout", "--hostname", "github.com"], expect.any(Number));
expect(harness.realtimeSignals.map((s) => s.channel)).toContain(CHANGED);
});
});
describe("Account Pool and devbox flows", () => {
it("proxies the Claude sign-in", async () => {
const { harness, rpcCalls } = await setup();
expect(await harness.callRpc("claudeSignIn", null)).toEqual({
sessionId: "00000000-0000-4000-8000-000000000001",
url: "https://claude.ai/oauth/authorize?x",
});
expect(await harness.callRpc("claudeComplete", { sessionId: "00000000-0000-4000-8000-000000000001", pasted: "code#state" })).toEqual({
label: "me@example.com",
});
expect(rpcCalls.map((c) => `${c.pluginId}:${c.method}`)).toEqual([`${ACCOUNT_POOL_ID}:login.start`, `${ACCOUNT_POOL_ID}:login.complete`]);
expect(rpcCalls[1].input).toEqual({ sessionId: "00000000-0000-4000-8000-000000000001", pasted: "code#state" });
});
it("proxies the Codex device flow", async () => {
const { harness } = await setup();
expect(await harness.callRpc("codexSignIn", null)).toEqual({
sessionId: "00000000-0000-4000-8000-000000000002",
url: "https://auth.openai.com/device",
code: "WXYZ-1234",
expiresAt: 1_700_000_000_000,
intervalMs: 5000,
});
expect(await harness.callRpc("codexPoll", { sessionId: "00000000-0000-4000-8000-000000000002" })).toEqual({
status: "complete",
label: "codex@example.com",
});
expect(await harness.callRpc("codexCancel", { sessionId: "00000000-0000-4000-8000-000000000002" })).toEqual({ ok: true });
});
it("starts devbox's connect", async () => {
const { harness } = await setup();
expect(await harness.callRpc("devboxConnect", null)).toEqual({ url: "https://devbox.example/connect/authorize?state=s" });
});
it("starts Linear's connect", async () => {
const { harness } = await setup();
expect(await harness.callRpc("linearConnect", null)).toEqual({ url: "https://linear.example/oauth/authorize?state=s" });
});
it("reports a plugin that does not answer", async () => {
const { harness, status } = await setup();
harness.sdk.stub("plugins.callRpc", async () => {
throw new Error("boom");
});
const s = await status();
expect(s.ai.available).toBe(false);
expect(s.ai.message).toContain("boom");
expect(s.devbox.available).toBe(false);
expect(s.linear.available).toBe(false);
});
});
describe("Figma step", () => {
it("is unavailable until a machine is connected", async () => {
const { status } = await setup();
const s = await status();
expect(s.steps.figma).toBe("unavailable");
expect(s.figma).toMatchObject({ available: false, machine: null });
expect(s.figma.message).toContain("No machine");
});
it("is unavailable when claude is not on the machine", async () => {
const { status } = await setup({ hosts: [{ name: "box" }], figmaHost: { claude: false, configured: false, authenticated: false, pending: false, url: null, message: "claude is not installed on this machine." } });
const s = await status();
expect(s.steps.figma).toBe("unavailable");
expect(s.figma).toMatchObject({ available: false, machine: "box", claude: false });
expect(s.figma.message).toContain("claude is not installed");
});
it("is optional before Figma is configured, so a server that never uses it stays complete", async () => {
const { status } = await setup({
builtInGit: { status: "logged in", statusMessage: "ok" },
pool: { routing: { claude: true, codex: false }, accounts: [{ id: "a", provider: "claude", label: "me", enabled: true, status: "ready" }] },
devbox: { connected: true, project: "dylan" },
linear: { connected: true, configured: true, user: { id: "u1", name: "jane", email: null }, organization: { name: "Acme", urlKey: "acme" } },
hosts: [{ name: "box" }],
runs: signedIn,
});
const s = await status();
expect(s.steps.figma).toBe("optional");
expect(s.figma).toMatchObject({ available: true, machine: "box", claude: true, configured: false, authenticated: false });
expect(s.complete).toBe(true);
});
it("is a to-do once configured but not signed in", async () => {
const { status } = await setup({ hosts: [{ name: "box" }], figmaHost: { claude: true, configured: true, authenticated: false, pending: false, url: null, message: null } });
expect((await status()).steps.figma).toBe("todo");
});
it("is a to-do while a sign-in waits for its callback", async () => {
const { status } = await setup({ hosts: [{ name: "box" }], figmaHost: { claude: true, configured: true, authenticated: false, pending: true, url: "https://www.figma.com/oauth/mcp?x", message: null } });
const s = await status();
expect(s.steps.figma).toBe("todo");
expect(s.figma.url).toBe("https://www.figma.com/oauth/mcp?x");
});
it("is done once Claude Code is signed in", async () => {
const { status } = await setup({ hosts: [{ name: "box" }], figmaHost: { claude: true, configured: true, authenticated: true, pending: false, url: null, message: null } });
expect((await status()).steps.figma).toBe("done");
});
it("caches the machine state so a poll does not repeat the host check", async () => {
const { status, hostCalls } = await setup({ hosts: [{ name: "box" }] });
await status();
await status();
expect(hostCalls.filter((call) => call.method === "state")).toHaveLength(1);
});
it("starts a sign-in on the connected machine and proxies completion", async () => {
const { harness, hostCalls, status } = await setup({ hosts: [{ id: "h1", name: "box" }] });
expect(await harness.callRpc("figmaSignIn", null)).toEqual({ url: "https://www.figma.com/oauth/mcp?response_type=code", port: 53280 });
expect(hostCalls[0]).toEqual({ method: "start", input: null, hostId: "h1" });
expect((await status()).figma.pending).toBe(true);
expect(await harness.callRpc("figmaComplete", { url: "http://localhost:53280/callback?code=c&state=s" })).toEqual({ ok: true, message: null });
expect(hostCalls.find((call) => call.method === "complete")).toMatchObject({ hostId: "h1", input: { url: "http://localhost:53280/callback?code=c&state=s" } });
expect(await harness.callRpc("figmaCancel", null)).toEqual({ ok: true });
});
it("passes a rejected callback back to the page", async () => {
const { harness } = await setup({ hosts: [{ name: "box" }], figmaComplete: { ok: false, message: "The callback URL has no code in it." } });
expect(await harness.callRpc("figmaComplete", { url: "http://localhost:53280/callback" })).toEqual({ ok: false, message: "The callback URL has no code in it." });
});
it("fails the sign-in when no machine is connected", async () => {
const { harness } = await setup();
await expect(harness.callRpc("figmaSignIn", null)).rejects.toThrow(/No machine/u);
});
});
describe("machine environment seeding", () => {
it("copies FIREWORKS_API_KEY from the server environment to every machine", async () => {
const { setVariables } = await setup({ env: { FIREWORKS_API_KEY: "fw-secret" } });
expect(setVariables).toEqual([{ name: "FIREWORKS_API_KEY", value: "fw-secret", note: SEEDED_NOTE }]);
});
it("sets nothing when the server has no key", async () => {
const { setVariables } = await setup({ env: { FIREWORKS_API_KEY: " ", OTHER: "x" } });
expect(setVariables).toEqual([]);
});
it("still loads when the machine environment cannot be written", async () => {
const { status, setVariables } = await setup({
env: { FIREWORKS_API_KEY: "fw-secret" },
setVariable: async () => {
throw new Error("encryption key unavailable");
},
});
expect(setVariables).toHaveLength(1);
const s = await status();
expect(s.complete).toBe(false);
});
});