1import {
2 AuthStandardClientScopes,
3 EnvironmentId,
4 PRIMARY_LOCAL_ENVIRONMENT_ID,
5 type DesktopBridge,
6 type DesktopSshEnvironmentBootstrap,
7 type DesktopSshEnvironmentTarget,
8} from "@t3tools/contracts";
9import { describe, expect, it } from "@effect/vitest";
10import * as Effect from "effect/Effect";
11import * as Fiber from "effect/Fiber";
12import { afterEach, vi } from "vite-plus/test";
13import { ClientCapabilities } from "@t3tools/client-runtime/platform";
14import {
15 ConnectionBlockedError,
16 ConnectionTransientError,
17} from "@t3tools/client-runtime/connection";
18
19import {
20 canRetainCachedPlatformRegistrationAfterRefreshFailure,
21 canReuseCachedPlatformRegistration,
22 layer,
23 isRejectedBootstrapCredentialError,
24 isRejectedSecondaryBootstrap,
25 nextRejectedSecondaryBootstrap,
26 primaryRegistrationToRetainAfterTopologyRead,
27 prepareDesktopSshEnvironment,
28 provisionDesktopSshEnvironment,
29 secondaryRegistrationsToRetainAfterTopologyRead,
30} from "./platform.ts";
31import { APP_VERSION } from "../branding.ts";
32
33const TARGET: DesktopSshEnvironmentTarget = {
34 alias: "devbox",
35 hostname: "devbox.example.test",
36 username: "developer",
37 port: 22,
38};
39
40const SSH_BOOTSTRAP = {
41 target: TARGET,
42 httpBaseUrl: "http://127.0.0.1:3201/",
43 wsBaseUrl: "ws://127.0.0.1:3201/",
44 pairingToken: "abandoned-token",
45};
46
47function pendingSetup() {
48 let resolve!: (bootstrap: DesktopSshEnvironmentBootstrap) => void;
49 let reject!: (cause: unknown) => void;
50 const promise = new Promise<DesktopSshEnvironmentBootstrap>((onResolve, onReject) => {
51 resolve = onResolve;
52 reject = onReject;
53 });
54 return { promise, resolve, reject };
55}
56
57afterEach(() => {
58 vi.unstubAllGlobals();
59 vi.restoreAllMocks();
60});
61
62function makeBridge(
63 calls: string[],
64 options?: {
65 readonly failDescriptor?: boolean;
66 readonly savedTokenRejected?: boolean;
67 readonly savedTokenPermissions?: ReadonlyArray<string>;
68 },
69): DesktopBridge {
70 return {
71 getClientPlatform: () => "linux",
72 getLocalEnvironmentEnabled: () => true,
73 ensureSshEnvironment: async (
74 target: DesktopSshEnvironmentTarget,
75 ensureOptions?: { issuePairingToken?: boolean },
76 ) => {
77 calls.push("ensure");
78 return {
79 target,
80 httpBaseUrl: "http://127.0.0.1:3201/",
81 wsBaseUrl: "ws://127.0.0.1:3201/",
82 pairingToken: ensureOptions?.issuePairingToken === true ? "pairing-token" : null,
83 };
84 },
85 disconnectSshEnvironment: async () => {
86 calls.push("disconnect");
87 },
88 fetchSshSessionState: async () => {
89 calls.push("session");
90 return {
91 authenticated: options?.savedTokenRejected !== true,
92 permissions: options?.savedTokenPermissions ?? AuthStandardClientScopes,
93 };
94 },
95 fetchSshEnvironmentDescriptor: async () => {
96 calls.push("descriptor");
97 if (options?.failDescriptor === true) {
98 throw new Error("descriptor unavailable");
99 }
100 return {
101 environmentId: EnvironmentId.make("environment-ssh"),
102 label: "SSH environment",
103 platform: {
104 os: "linux",
105 arch: "x64",
106 },
107 serverVersion: "0.0.0-test",
108 capabilities: {
109 repositoryIdentity: true,
110 },
111 };
112 },
113 bootstrapSshBearerSession: async () => {
114 calls.push("token");
115 return {
116 access_token: "bearer-token",
117 issued_token_type: "urn:ietf:params:oauth:token-type:access_token",
118 token_type: "Bearer",
119 expires_in: 3_600,
120 scope: AuthStandardClientScopes.join(" "),
121 };
122 },
123 } as unknown as DesktopBridge;
124}
125
126describe("desktop SSH pairing", () => {
127 it.effect("does not consume the credential when adding a route to a different machine", () =>
128 Effect.gen(function* () {
129 const calls: string[] = [];
130
131 const error = yield* provisionDesktopSshEnvironment(
132 makeBridge(calls),
133 TARGET,
134 EnvironmentId.make("some-other-machine"),
135 ).pipe(Effect.flip);
136
137 expect(error).toMatchObject({ reason: "configuration" });
138 expect(calls).toEqual(["ensure", "descriptor"]);
139 }),
140 );
141
142 it.effect("exchanges one setup credential after repeated interrupted provisions", () =>
143 Effect.gen(function* () {
144 const bridge = makeBridge([]);
145 const setup = pendingSetup();
146 const ensure = vi.spyOn(bridge, "ensureSshEnvironment").mockReturnValue(setup.promise);
147 const exchange = vi.spyOn(bridge, "bootstrapSshBearerSession");
148 for (let attempt = 0; attempt < 2; attempt += 1) {
149 const interrupted = yield* Effect.forkChild(
150 provisionDesktopSshEnvironment(bridge, TARGET),
151 { startImmediately: true },
152 );
153 yield* Fiber.interrupt(interrupted);
154 }
155
156 const retry = yield* Effect.forkChild(provisionDesktopSshEnvironment(bridge, TARGET), {
157 startImmediately: true,
158 });
159 setup.resolve(SSH_BOOTSTRAP);
160 const provisioned = yield* Fiber.join(retry);
161
162 expect(provisioned.bearerToken).toBe("bearer-token");
163 expect(ensure).toHaveBeenCalledExactlyOnceWith(TARGET, { issuePairingToken: true });
164 expect(exchange).toHaveBeenCalledExactlyOnceWith(
165 SSH_BOOTSTRAP.httpBaseUrl,
166 "abandoned-token",
167 );
168 }),
169 );
170
171 it.effect("starts fresh after an abandoned setup settles in success or failure", () =>
172 Effect.gen(function* () {
173 for (const outcome of ["success", "failure"]) {
174 const bridge = makeBridge([]);
175 const setup = pendingSetup();
176 const ensure = vi.spyOn(bridge, "ensureSshEnvironment").mockReturnValueOnce(setup.promise);
177 const exchange = vi.spyOn(bridge, "bootstrapSshBearerSession");
178 const interrupted = yield* Effect.forkChild(
179 provisionDesktopSshEnvironment(bridge, TARGET),
180 { startImmediately: true },
181 );
182 yield* Fiber.interrupt(interrupted);
183 if (outcome === "success") setup.resolve(SSH_BOOTSTRAP);
184 else setup.reject(new Error("remote launch failed"));
185 yield* Effect.promise(() =>
186 setup.promise.then(
187 () => undefined,
188 () => undefined,
189 ),
190 );
191
192 const provisioned = yield* provisionDesktopSshEnvironment(bridge, TARGET);
193
194 expect(provisioned.bearerToken).toBe("bearer-token");
195 expect(ensure).toHaveBeenCalledTimes(2);
196 expect(exchange).toHaveBeenCalledExactlyOnceWith(
197 SSH_BOOTSTRAP.httpBaseUrl,
198 "pairing-token",
199 );
200 }
201 }),
202 );
203
204 it.effect("disconnect invalidates unfinished setup even before provision is interrupted", () =>
205 Effect.gen(function* () {
206 const calls: string[] = [];
207 const bridge = makeBridge(calls);
208 const setup = pendingSetup();
209 const ensure = vi.spyOn(bridge, "ensureSshEnvironment").mockReturnValueOnce(setup.promise);
210 const exchange = vi.spyOn(bridge, "bootstrapSshBearerSession");
211 vi.stubGlobal("window", { desktopBridge: bridge });
212 const ssh = yield* ClientCapabilities.SshEnvironmentGateway.pipe(Effect.provide(layer));
213 const interrupted = yield* Effect.forkChild(ssh.provision(TARGET), {
214 startImmediately: true,
215 });
216 yield* ssh.disconnect(TARGET);
217 yield* Fiber.interrupt(interrupted);
218
219 const retry = yield* Effect.forkChild(ssh.provision(TARGET), { startImmediately: true });
220 setup.resolve(SSH_BOOTSTRAP);
221 const provisioned = yield* Fiber.join(retry);
222
223 expect(provisioned.bearerToken).toBe("bearer-token");
224 expect(ensure).toHaveBeenCalledTimes(2);
225 expect(calls).toEqual(["disconnect", "ensure", "descriptor", "token"]);
226 expect(exchange).toHaveBeenCalledExactlyOnceWith(SSH_BOOTSTRAP.httpBaseUrl, "pairing-token");
227 }),
228 );
229
230 const failPairing = (serverVersion: string, message: string) => {
231 const bridge = makeBridge([]);
232 const fetchDescriptor = bridge.fetchSshEnvironmentDescriptor;
233 vi.spyOn(bridge, "fetchSshEnvironmentDescriptor").mockImplementation(async (url) => ({
234 ...(await fetchDescriptor(url)),
235 serverVersion,
236 }));
237 vi.spyOn(bridge, "bootstrapSshBearerSession").mockRejectedValue(new Error(message));
238 return provisionDesktopSshEnvironment(bridge, TARGET).pipe(Effect.flip);
239 };
240 const PAIRING_500 =
241 "[ssh_http:500] SSH remote API request failed during bootstrap-bearer-session.";
242
243 it.effect("blocks with both versions when a server on another build rejects pairing", () =>
244 Effect.gen(function* () {
245 const error = yield* failPairing("0.0.46-nightly.20261004.2652", PAIRING_500);
246
247 expect(error).toBeInstanceOf(ConnectionBlockedError);
248 expect(error.detail).toContain("0.0.46-nightly.20261004.2652");
249 expect(error.detail).toContain(APP_VERSION);
250 }),
251 );
252
253 it.effect("keeps retrying pairing failures that are not a version mismatch", () =>
254 Effect.gen(function* () {
255 expect(yield* failPairing(APP_VERSION, PAIRING_500)).toBeInstanceOf(ConnectionTransientError);
256 expect(
257 yield* failPairing(
258 "0.0.46-nightly.20261004.2652",
259 "SSH remote API request failed during bootstrap-bearer-session.",
260 ),
261 ).toBeInstanceOf(ConnectionTransientError);
262 }),
263 );
264
265 it.effect("reconnects with the saved token while it holds every standard scope", () =>
266 Effect.gen(function* () {
267 const calls: string[] = [];
268
269 const bridge = makeBridge(calls);
270 const ensure = vi.spyOn(bridge, "ensureSshEnvironment");
271 const prepared = yield* prepareDesktopSshEnvironment(bridge, {
272 target: TARGET,
273 expectedEnvironmentId: EnvironmentId.make("environment-ssh"),
274 bearerToken: "saved-token",
275 });
276
277 expect(prepared.bearerToken).toBe("saved-token");
278 expect(ensure).toHaveBeenCalledExactlyOnceWith(TARGET, { issuePairingToken: false });
279 expect(calls).toEqual(["ensure", "descriptor", "session"]);
280 }),
281 );
282
283 it.effect("pairs again when the host no longer accepts the saved token", () =>
284 Effect.gen(function* () {
285 const calls: string[] = [];
286
287 const prepared = yield* prepareDesktopSshEnvironment(
288 makeBridge(calls, { savedTokenRejected: true }),
289 {
290 target: TARGET,
291 expectedEnvironmentId: EnvironmentId.make("environment-ssh"),
292 bearerToken: "saved-token",
293 },
294 );
295
296 expect(prepared.bearerToken).toBe("bearer-token");
297 expect(calls).toEqual(["ensure", "descriptor", "session", "ensure", "descriptor", "token"]);
298 }),
299 );
300
301 it.effect("pairs again when the saved token predates a standard scope", () =>
302 Effect.gen(function* () {
303 const calls: string[] = [];
304
305 const prepared = yield* prepareDesktopSshEnvironment(
306 makeBridge(calls, {
307 savedTokenPermissions: ["orchestration:read", "orchestration:operate"],
308 }),
309 {
310 target: TARGET,
311 expectedEnvironmentId: EnvironmentId.make("environment-ssh"),
312 bearerToken: "saved-token",
313 },
314 );
315
316 expect(prepared.bearerToken).toBe("bearer-token");
317 expect(calls).toEqual(["ensure", "descriptor", "session", "ensure", "descriptor", "token"]);
318 }),
319 );
320
321 it.effect("stops before sending the saved token or pairing on a different machine", () =>
322 Effect.gen(function* () {
323 const calls: string[] = [];
324
325 const error = yield* prepareDesktopSshEnvironment(makeBridge(calls), {
326 target: TARGET,
327 expectedEnvironmentId: EnvironmentId.make("some-other-machine"),
328 bearerToken: "saved-token",
329 }).pipe(Effect.flip);
330
331 expect(error).toMatchObject({ reason: "configuration" });
332 expect(calls).toEqual(["ensure", "descriptor"]);
333 }),
334 );
335});
336
337describe("desktop-local bearer cache", () => {
338 const registration = {} as never;
339
340 it("retains only unexpired secondaries after a topology read failure", () => {
341 const valid = {
342 expiresAtEpochMs: 20_000,
343 signature: "valid-secondary",
344 registration,
345 refreshAtEpochMs: 15_000,
346 };
347 const previous = new Map([
348 ["valid-secondary", valid],
349 [
350 "expired-secondary",
351 {
352 expiresAtEpochMs: 10_000,
353 signature: "expired-secondary",
354 registration,
355 refreshAtEpochMs: 5_000,
356 },
357 ],
358 ]);
359
360 expect(
361 secondaryRegistrationsToRetainAfterTopologyRead(
362 previous,
363 { _tag: "Failure", cause: new Error("IPC unavailable") },
364 10_000,
365 ),
366 ).toEqual(new Map([["valid-secondary", valid]]));
367 });
368
369 it("treats a successful empty topology as authoritative removal", () => {
370 const previous = new Map([
371 [
372 "secondary",
373 {
374 expiresAtEpochMs: 20_000,
375 signature: "secondary",
376 registration,
377 refreshAtEpochMs: 15_000,
378 },
379 ],
380 ]);
381
382 expect(
383 secondaryRegistrationsToRetainAfterTopologyRead(
384 previous,
385 { _tag: "Success", bootstraps: [] },
386 10_000,
387 ),
388 ).toEqual(new Map());
389 });
390});
391
392describe("rejected desktop-local bootstrap tokens", () => {
393 it("retries at once when the token or endpoint changes", () => {
394 const rejected = nextRejectedSecondaryBootstrap(undefined, "http://a|ws://a|old-token", 0);
395
396 expect(isRejectedSecondaryBootstrap(rejected, "http://a|ws://a|new-token", 1)).toBe(false);
397 expect(isRejectedSecondaryBootstrap(rejected, "http://b|ws://b|old-token", 1)).toBe(false);
398 expect(isRejectedSecondaryBootstrap(undefined, "http://a|ws://a|old-token", 1)).toBe(false);
399 expect(nextRejectedSecondaryBootstrap(rejected, "http://a|ws://a|new-token", 1).delayMs).toBe(
400 60_000,
401 );
402 });
403
404 // regression: #16273 a rejected desktop-local credential was retried every poll
405 it("treats only authentication rejections as a dead credential", () => {
406 expect(
407 isRejectedBootstrapCredentialError(
408 new ConnectionBlockedError({ reason: "authentication", detail: "invalid" }),
409 ),
410 ).toBe(true);
411 expect(
412 isRejectedBootstrapCredentialError(
413 new ConnectionTransientError({ reason: "endpoint-unavailable", detail: "booting" }),
414 ),
415 ).toBe(false);
416 });
417});
418
419describe("desktop-local bearer cache across token changes", () => {
420 it("keeps a live bearer when only the bootstrap token rotates", () => {
421 const cached = {
422 signature: "http://a|ws://a",
423 registration: {} as never,
424 expiresAtEpochMs: 20_000,
425 refreshAtEpochMs: 15_000,
426 };
427
428 expect(canReuseCachedPlatformRegistration(cached, "http://a|ws://a", 10_000)).toBe(true);
429 expect(
430 canRetainCachedPlatformRegistrationAfterRefreshFailure(cached, "http://a|ws://a", 16_000),
431 ).toBe(true);
432 expect(canReuseCachedPlatformRegistration(cached, "http://b|ws://b", 10_000)).toBe(false);
433 });
434});
435
436describe("primary topology cache", () => {
437 const registration = {} as never;
438 const cached = {
439 signature: "primary|http://127.0.0.1:3773/|ws://127.0.0.1:3773/",
440 registration,
441 };
442 const previous = new Map([[PRIMARY_LOCAL_ENVIRONMENT_ID, cached]]);
443
444 it("retains the cached primary after a transient topology read failure", () => {
445 expect(
446 primaryRegistrationToRetainAfterTopologyRead(previous, {
447 _tag: "Failure",
448 cause: new Error("IPC unavailable"),
449 }),
450 ).toBe(cached);
451 });
452});