1import {
2 AuthStandardClientScopes,
3 EnvironmentId,
4 PRIMARY_LOCAL_ENVIRONMENT_ID,
5 type DesktopBridge,
6 type DesktopSshEnvironmentTarget,
7} from "@t3tools/contracts";
8import { describe, expect, it } from "@effect/vitest";
9import * as Effect from "effect/Effect";
10import {
11 ConnectionBlockedError,
12 ConnectionTransientError,
13} from "@t3tools/client-runtime/connection";
14
15import {
16 canRetainCachedPlatformRegistrationAfterRefreshFailure,
17 canReuseCachedPlatformRegistration,
18 isRejectedBootstrapCredentialError,
19 isRejectedSecondaryBootstrap,
20 nextRejectedSecondaryBootstrap,
21 primaryRegistrationToRetainAfterTopologyRead,
22 provisionDesktopSshEnvironment,
23 readPrimaryEnvironmentTargetResult,
24 secondaryRegistrationsToRetainAfterTopologyRead,
25 secondaryBearerExpiresAtEpochMs,
26 secondaryBearerRefreshAtEpochMs,
27} from "./platform.ts";
28
29const TARGET: DesktopSshEnvironmentTarget = {
30 alias: "devbox",
31 hostname: "devbox.example.test",
32 username: "developer",
33 port: 22,
34};
35
36function makeBridge(
37 calls: string[],
38 options?: { readonly failDescriptor?: boolean },
39): DesktopBridge {
40 return {
41 ensureSshEnvironment: async (target: DesktopSshEnvironmentTarget) => {
42 calls.push("ensure");
43 return {
44 target,
45 httpBaseUrl: "http://127.0.0.1:3201/",
46 wsBaseUrl: "ws://127.0.0.1:3201/",
47 pairingToken: "pairing-token",
48 };
49 },
50 fetchSshEnvironmentDescriptor: async () => {
51 calls.push("descriptor");
52 if (options?.failDescriptor === true) {
53 throw new Error("descriptor unavailable");
54 }
55 return {
56 environmentId: EnvironmentId.make("environment-ssh"),
57 label: "SSH environment",
58 platform: {
59 os: "linux",
60 arch: "x64",
61 },
62 serverVersion: "0.0.0-test",
63 capabilities: {
64 repositoryIdentity: true,
65 },
66 };
67 },
68 bootstrapSshBearerSession: async () => {
69 calls.push("token");
70 return {
71 access_token: "bearer-token",
72 issued_token_type: "urn:ietf:params:oauth:token-type:access_token",
73 token_type: "Bearer",
74 expires_in: 3_600,
75 scope: AuthStandardClientScopes.join(" "),
76 };
77 },
78 } as unknown as DesktopBridge;
79}
80
81describe("desktop SSH pairing", () => {
82 it.effect("fetches the descriptor before consuming the one-time credential", () =>
83 Effect.gen(function* () {
84 const calls: string[] = [];
85
86 const provisioned = yield* provisionDesktopSshEnvironment(makeBridge(calls), TARGET);
87
88 expect(provisioned.environmentId).toBe(EnvironmentId.make("environment-ssh"));
89 expect(calls).toEqual(["ensure", "descriptor", "token"]);
90 }),
91 );
92
93 it.effect("does not consume the credential when adding a route to a different machine", () =>
94 Effect.gen(function* () {
95 const calls: string[] = [];
96
97 const error = yield* provisionDesktopSshEnvironment(
98 makeBridge(calls),
99 TARGET,
100 EnvironmentId.make("some-other-machine"),
101 ).pipe(Effect.flip);
102
103 expect(error).toMatchObject({ reason: "configuration" });
104 expect(calls).toEqual(["ensure", "descriptor"]);
105 }),
106 );
107
108 it.effect("does not consume the credential when descriptor discovery fails", () =>
109 Effect.gen(function* () {
110 const calls: string[] = [];
111
112 yield* provisionDesktopSshEnvironment(
113 makeBridge(calls, { failDescriptor: true }),
114 TARGET,
115 ).pipe(Effect.flip);
116
117 expect(calls).toEqual(["ensure", "descriptor"]);
118 }),
119 );
120});
121
122describe("desktop-local bearer cache", () => {
123 const registration = {} as never;
124
125 it("refreshes a secondary bearer before it expires", () => {
126 const issuedAtEpochMs = 10_000;
127 const refreshAtEpochMs = secondaryBearerRefreshAtEpochMs(issuedAtEpochMs, 60);
128 const expiresAtEpochMs = secondaryBearerExpiresAtEpochMs(issuedAtEpochMs, 60);
129 const cached = {
130 expiresAtEpochMs,
131 signature: "secondary-signature",
132 registration,
133 refreshAtEpochMs,
134 };
135
136 expect(refreshAtEpochMs).toBe(65_000);
137 expect(canReuseCachedPlatformRegistration(cached, cached.signature, 64_999)).toBe(true);
138 expect(canReuseCachedPlatformRegistration(cached, cached.signature, 65_000)).toBe(false);
139 expect(
140 canRetainCachedPlatformRegistrationAfterRefreshFailure(cached, cached.signature, 69_999),
141 ).toBe(true);
142 expect(
143 canRetainCachedPlatformRegistrationAfterRefreshFailure(cached, cached.signature, 70_000),
144 ).toBe(false);
145 });
146
147 it("does not cache credentials whose lifetime is shorter than the refresh skew", () => {
148 const refreshAtEpochMs = secondaryBearerRefreshAtEpochMs(10_000, 3);
149 const cached = {
150 expiresAtEpochMs: secondaryBearerExpiresAtEpochMs(10_000, 3),
151 signature: "secondary-signature",
152 registration,
153 refreshAtEpochMs,
154 };
155
156 expect(refreshAtEpochMs).toBe(10_000);
157 expect(canReuseCachedPlatformRegistration(cached, cached.signature, 10_000)).toBe(false);
158 });
159
160 it("retains only unexpired secondaries after a topology read failure", () => {
161 const valid = {
162 expiresAtEpochMs: 20_000,
163 signature: "valid-secondary",
164 registration,
165 refreshAtEpochMs: 15_000,
166 };
167 const previous = new Map([
168 ["valid-secondary", valid],
169 [
170 "expired-secondary",
171 {
172 expiresAtEpochMs: 10_000,
173 signature: "expired-secondary",
174 registration,
175 refreshAtEpochMs: 5_000,
176 },
177 ],
178 ]);
179
180 expect(
181 secondaryRegistrationsToRetainAfterTopologyRead(
182 previous,
183 { _tag: "Failure", cause: new Error("IPC unavailable") },
184 10_000,
185 ),
186 ).toEqual(new Map([["valid-secondary", valid]]));
187 });
188
189 it("treats a successful empty topology as authoritative removal", () => {
190 const previous = new Map([
191 [
192 "secondary",
193 {
194 expiresAtEpochMs: 20_000,
195 signature: "secondary",
196 registration,
197 refreshAtEpochMs: 15_000,
198 },
199 ],
200 ]);
201
202 expect(
203 secondaryRegistrationsToRetainAfterTopologyRead(
204 previous,
205 { _tag: "Success", bootstraps: [] },
206 10_000,
207 ),
208 ).toEqual(new Map());
209 });
210});
211
212describe("rejected desktop-local bootstrap tokens", () => {
213 it("backs off on a rejected signature and retries it on a growing, capped delay", () => {
214 const signature = "http://a|ws://a|old-token";
215 const first = nextRejectedSecondaryBootstrap(undefined, signature, 0);
216
217 expect(isRejectedSecondaryBootstrap(first, signature, 59_999)).toBe(true);
218 // A backend restarted on the same port accepts the same token again, so it is retried.
219 expect(isRejectedSecondaryBootstrap(first, signature, 60_000)).toBe(false);
220
221 const second = nextRejectedSecondaryBootstrap(first, signature, 60_000);
222 expect(second.retryAtEpochMs).toBe(180_000);
223
224 let backoff = second;
225 for (let attempt = 0; attempt < 10; attempt += 1) {
226 backoff = nextRejectedSecondaryBootstrap(backoff, signature, 0);
227 }
228 expect(backoff.delayMs).toBe(30 * 60_000);
229 });
230
231 it("retries at once when the token or endpoint changes", () => {
232 const rejected = nextRejectedSecondaryBootstrap(undefined, "http://a|ws://a|old-token", 0);
233
234 expect(isRejectedSecondaryBootstrap(rejected, "http://a|ws://a|new-token", 1)).toBe(false);
235 expect(isRejectedSecondaryBootstrap(rejected, "http://b|ws://b|old-token", 1)).toBe(false);
236 expect(isRejectedSecondaryBootstrap(undefined, "http://a|ws://a|old-token", 1)).toBe(false);
237 expect(nextRejectedSecondaryBootstrap(rejected, "http://a|ws://a|new-token", 1).delayMs).toBe(
238 60_000,
239 );
240 });
241
242 it("treats only authentication rejections as a dead credential", () => {
243 expect(
244 isRejectedBootstrapCredentialError(
245 new ConnectionBlockedError({ reason: "authentication", detail: "invalid" }),
246 ),
247 ).toBe(true);
248 expect(
249 isRejectedBootstrapCredentialError(
250 new ConnectionTransientError({ reason: "endpoint-unavailable", detail: "booting" }),
251 ),
252 ).toBe(false);
253 });
254});
255
256describe("desktop-local bearer cache across token changes", () => {
257 it("keeps a live bearer when only the bootstrap token rotates", () => {
258 // Cached by endpoint: a rotated token reuses the bearer instead of
259 // dropping the environment (and its drafts) when the new token is rejected.
260 const cached = {
261 signature: "http://a|ws://a",
262 registration: {} as never,
263 expiresAtEpochMs: 20_000,
264 refreshAtEpochMs: 15_000,
265 };
266
267 expect(canReuseCachedPlatformRegistration(cached, "http://a|ws://a", 10_000)).toBe(true);
268 expect(
269 canRetainCachedPlatformRegistrationAfterRefreshFailure(cached, "http://a|ws://a", 16_000),
270 ).toBe(true);
271 expect(canReuseCachedPlatformRegistration(cached, "http://b|ws://b", 10_000)).toBe(false);
272 });
273});
274
275describe("primary topology cache", () => {
276 const registration = {} as never;
277 const cached = {
278 signature: "primary|http://127.0.0.1:3773/|ws://127.0.0.1:3773/",
279 registration,
280 };
281 const previous = new Map([[PRIMARY_LOCAL_ENVIRONMENT_ID, cached]]);
282
283 it("captures synchronous primary target read failures", () => {
284 const cause = new Error("invalid primary target");
285
286 expect(
287 readPrimaryEnvironmentTargetResult(() => {
288 throw cause;
289 }),
290 ).toEqual({ _tag: "Failure", cause });
291 });
292
293 it("retains the cached primary after a transient topology read failure", () => {
294 expect(
295 primaryRegistrationToRetainAfterTopologyRead(previous, {
296 _tag: "Failure",
297 cause: new Error("IPC unavailable"),
298 }),
299 ).toBe(cached);
300 });
301
302 it("treats a successful primary absence as authoritative removal", () => {
303 expect(
304 primaryRegistrationToRetainAfterTopologyRead(previous, {
305 _tag: "Success",
306 target: null,
307 }),
308 ).toBeUndefined();
309 });
310});