Skip to content
Source
Sign inSign up

storageDocument.test.ts

410 lines14.1 KB

1import { EnvironmentId } from "@t3tools/contracts";
2import { describe, expect, it } from "@effect/vitest";
3import * as Schema from "effect/Schema";
4import * as Effect from "effect/Effect";
5import * as Option from "effect/Option";
6​
7import * as TokenStore from "../authorization/tokenStore.ts";
8import {
9 BearerConnectionCredential,
10 BearerConnectionProfile,
11 BearerConnectionRegistration,
12 RelayConnectionRegistration,
13 SshConnectionProfile,
14 SshConnectionRegistration,
15} from "../connection/catalog.ts";
16import {
17 BearerConnectionTarget,
18 ConnectionTransientError,
19 PrimaryConnectionTarget,
20 RelayConnectionTarget,
21 SshConnectionTarget,
22} from "../connection/model.ts";
23import {
24 PullRequestRoutingPermissions,
25 makePullRequestRoutingPermissions,
26} from "../connection/pullRequestRoutingPermissions.ts";
27import {
28 ConnectionCatalogDocument,
29 EMPTY_CONNECTION_CATALOG_DOCUMENT,
30 catalogRoutes,
31 setRoutesInCatalog,
32 putRemoteDpopTokenInCatalog,
33 registerConnectionInCatalog,
34 removeConnectionFromCatalog,
35 setConnectionEnabledInCatalog,
36} from "./storageDocument.ts";
37​
38const decodeConnectionCatalogDocument = Schema.decodeUnknownEffect(ConnectionCatalogDocument);
39​
40const ENVIRONMENT_ID = EnvironmentId.make("environment-1");
41const decodeCatalogDocument = Schema.decodeUnknownSync(ConnectionCatalogDocument);
42​
43const RELAY_TARGET = new RelayConnectionTarget({
44 environmentId: ENVIRONMENT_ID,
45 label: "Remote",
46});
47const BEARER_TARGET = new BearerConnectionTarget({
48 environmentId: ENVIRONMENT_ID,
49 label: "Remote",
50 connectionId: "bearer-1",
51});
52const BEARER_PROFILE = new BearerConnectionProfile({
53 connectionId: BEARER_TARGET.connectionId,
54 environmentId: ENVIRONMENT_ID,
55 label: BEARER_TARGET.label,
56 httpBaseUrl: "https://remote.example.test",
57 wsBaseUrl: "wss://remote.example.test",
58});
59const BEARER_CREDENTIAL = new BearerConnectionCredential({
60 token: "bearer-token",
61});
62const REMOTE_TOKEN = new TokenStore.RemoteDpopAccessToken({
63 environmentId: ENVIRONMENT_ID,
64 label: "Remote",
65 endpoint: {
66 httpBaseUrl: "https://remote.example.test",
67 wsBaseUrl: "wss://remote.example.test",
68 providerKind: "cloudflare_tunnel",
69 },
70 accessToken: "dpop-token",
71 expiresAtEpochMs: 1_000_000,
72 dpopThumbprint: "thumbprint",
73});
74​
75describe("ConnectionCatalogDocument", () => {
76 it.effect("persists explicit GitHub trust and forgets it when a connection is removed", () =>
77 Effect.gen(function* () {
78 let document = EMPTY_CONNECTION_CATALOG_DOCUMENT;
79 const entry = { target: BEARER_TARGET, profile: Option.some(BEARER_PROFILE), enabled: true };
80 const storage = {
81 read: Effect.sync(() => document.githubRoutingPermissions ?? []),
82 write: (githubRoutingPermissions: NonNullable<typeof document.githubRoutingPermissions>) =>
83 Effect.gen(function* () {
84 document = yield* decodeConnectionCatalogDocument({
85 ...document,
86 githubRoutingPermissions,
87 }).pipe(Effect.orDie);
88 }),
89 };
90 const permissions = yield* makePullRequestRoutingPermissions(storage);
91 expect(yield* permissions.get(entry)).toBe("off");
92 yield* permissions.set(entry, "read");
93 const restarted = yield* makePullRequestRoutingPermissions(storage);
94 expect(yield* restarted.get(entry)).toBe("read");
95​
96 const changedEndpoint = {
97 ...entry,
98 profile: Option.some(
99 new BearerConnectionProfile({
100 ...BEARER_PROFILE,
101 httpBaseUrl: "https://different.example.test",
102 }),
103 ),
104 };
105 expect(yield* restarted.get(changedEndpoint)).toBe("off");
106 expect(
107 yield* restarted.get({
108 ...entry,
109 profile: Option.some(
110 new BearerConnectionProfile({
111 ...BEARER_PROFILE,
112 wsBaseUrl: "wss://different.example.test",
113 }),
114 ),
115 }),
116 ).toBe("off");
117 expect(
118 yield* restarted.get({ target: RELAY_TARGET, profile: Option.none(), enabled: true }),
119 ).toBe("off");
120 yield* restarted.set(entry, "read-write");
121 expect(yield* restarted.get(entry)).toBe("read-write");
122 yield* restarted.forget(ENVIRONMENT_ID);
123 expect(yield* restarted.get(entry)).toBe("off");
124 const afterRemoval = yield* makePullRequestRoutingPermissions(storage);
125 expect(yield* afterRemoval.get(entry)).toBe("off");
126​
127 yield* permissions.set(entry, "read");
128 document = removeConnectionFromCatalog(document, ENVIRONMENT_ID);
129 const afterCatalogRemoval = yield* makePullRequestRoutingPermissions(storage);
130 expect(yield* afterCatalogRemoval.get(entry)).toBe("off");
131 }),
132 );
133​
134 it.effect("requires explicit trust for primary, relay, and SSH connections independently", () =>
135 Effect.gen(function* () {
136 const permissions = yield* makePullRequestRoutingPermissions({
137 read: Effect.succeed([]),
138 write: () => Effect.void,
139 });
140 const entries = [
141 {
142 target: new PrimaryConnectionTarget({
143 environmentId: ENVIRONMENT_ID,
144 label: "Local",
145 httpBaseUrl: "http://localhost:3000",
146 wsBaseUrl: "ws://localhost:3000",
147 }),
148 profile: Option.none(),
149 enabled: true,
150 },
151 { target: RELAY_TARGET, profile: Option.none(), enabled: true },
152 {
153 enabled: true,
154 target: new SshConnectionTarget({
155 environmentId: ENVIRONMENT_ID,
156 label: "SSH",
157 connectionId: "ssh-1",
158 }),
159 profile: Option.some(
160 new SshConnectionProfile({
161 environmentId: ENVIRONMENT_ID,
162 label: "SSH",
163 connectionId: "ssh-1",
164 target: { alias: "work", hostname: "work.example.test", username: "maria", port: 22 },
165 }),
166 ),
167 },
168 ];
169 for (const entry of entries) {
170 expect(yield* permissions.get(entry)).toBe("off");
171 yield* permissions.set(entry, "read");
172 expect(yield* permissions.get(entry)).toBe("read");
173 yield* permissions.set(entry, "off");
174 expect(yield* permissions.get(entry)).toBe("off");
175 }
176 }),
177 );
178​
179 it.effect("does not enable GitHub routing when permission persistence fails", () =>
180 Effect.gen(function* () {
181 const entry = { target: BEARER_TARGET, profile: Option.some(BEARER_PROFILE), enabled: true };
182 expect(yield* (yield* PullRequestRoutingPermissions).get(entry)).toBe("off");
183 const permissions = yield* makePullRequestRoutingPermissions({
184 read: Effect.succeed([]),
185 write: () =>
186 Effect.fail(
187 new ConnectionTransientError({
188 reason: "remote-unavailable",
189 detail: "storage unavailable",
190 }),
191 ),
192 });
193 yield* permissions.set(entry, "read-write").pipe(Effect.flip);
194 expect(yield* permissions.get(entry)).toBe("off");
195 }),
196 );
197​
198 it("replaces obsolete connection metadata without discarding a reusable DPoP token", () => {
199 const bearer = registerConnectionInCatalog(
200 {
201 ...EMPTY_CONNECTION_CATALOG_DOCUMENT,
202 remoteDpopTokens: [REMOTE_TOKEN],
203 },
204 new BearerConnectionRegistration({
205 target: BEARER_TARGET,
206 profile: BEARER_PROFILE,
207 credential: BEARER_CREDENTIAL,
208 }),
209 );
210 const relayTarget = new RelayConnectionTarget({
211 environmentId: ENVIRONMENT_ID,
212 label: "Remote",
213 });
214 const relay = registerConnectionInCatalog(
215 bearer,
216 new RelayConnectionRegistration({ target: relayTarget }),
217 );
218​
219 expect(relay.targets).toEqual([relayTarget]);
220 expect(relay.profiles).toEqual([]);
221 expect(relay.credentials).toEqual([]);
222 expect(relay.remoteDpopTokens).toEqual([REMOTE_TOKEN]);
223 });
224​
225 it("removes every catalog record owned by an explicit disconnect", () => {
226 const registered = registerConnectionInCatalog(
227 {
228 ...EMPTY_CONNECTION_CATALOG_DOCUMENT,
229 remoteDpopTokens: [REMOTE_TOKEN],
230 },
231 new BearerConnectionRegistration({
232 target: BEARER_TARGET,
233 profile: BEARER_PROFILE,
234 credential: BEARER_CREDENTIAL,
235 }),
236 );
237​
238 expect(removeConnectionFromCatalog(registered, ENVIRONMENT_ID)).toEqual(
239 EMPTY_CONNECTION_CATALOG_DOCUMENT,
240 );
241 });
242​
243 it("stores a DPoP token for a registered relay environment", () => {
244 const registered = registerConnectionInCatalog(
245 EMPTY_CONNECTION_CATALOG_DOCUMENT,
246 new RelayConnectionRegistration({ target: RELAY_TARGET }),
247 );
248​
249 expect(putRemoteDpopTokenInCatalog(registered, REMOTE_TOKEN)).toEqual({
250 ...registered,
251 remoteDpopTokens: [REMOTE_TOKEN],
252 });
253 });
254​
255 it("ignores a late DPoP token after the environment is removed", () => {
256 const registered = registerConnectionInCatalog(
257 EMPTY_CONNECTION_CATALOG_DOCUMENT,
258 new RelayConnectionRegistration({ target: RELAY_TARGET }),
259 );
260 const removed = removeConnectionFromCatalog(registered, ENVIRONMENT_ID);
261​
262 expect(putRemoteDpopTokenInCatalog(removed, REMOTE_TOKEN)).toEqual(
263 EMPTY_CONNECTION_CATALOG_DOCUMENT,
264 );
265 });
266​
267 it("does not store DPoP tokens for a different environment or connection kind", () => {
268 const bearer = registerConnectionInCatalog(
269 EMPTY_CONNECTION_CATALOG_DOCUMENT,
270 new BearerConnectionRegistration({
271 target: BEARER_TARGET,
272 profile: BEARER_PROFILE,
273 credential: BEARER_CREDENTIAL,
274 }),
275 );
276 const otherRelay = registerConnectionInCatalog(
277 EMPTY_CONNECTION_CATALOG_DOCUMENT,
278 new RelayConnectionRegistration({
279 target: new RelayConnectionTarget({
280 environmentId: EnvironmentId.make("environment-2"),
281 label: "Other relay",
282 }),
283 }),
284 );
285​
286 expect(putRemoteDpopTokenInCatalog(bearer, REMOTE_TOKEN)).toBe(bearer);
287 expect(putRemoteDpopTokenInCatalog(otherRelay, REMOTE_TOKEN)).toBe(otherRelay);
288 });
289​
290 it("decodes a document written before the disabled list existed", () => {
291 const decoded = decodeCatalogDocument({
292 schemaVersion: 1,
293 targets: [],
294 profiles: [],
295 credentials: [],
296 remoteDpopTokens: [],
297 });
298​
299 expect(decoded.disabledEnvironmentIds).toEqual([]);
300 });
301​
302 it("switches a saved environment off and back on without touching its records", () => {
303 const registered = registerConnectionInCatalog(
304 EMPTY_CONNECTION_CATALOG_DOCUMENT,
305 new BearerConnectionRegistration({
306 target: BEARER_TARGET,
307 profile: BEARER_PROFILE,
308 credential: BEARER_CREDENTIAL,
309 }),
310 );
311​
312 const disabled = setConnectionEnabledInCatalog(registered, ENVIRONMENT_ID, false);
313 expect(disabled.disabledEnvironmentIds).toEqual([ENVIRONMENT_ID]);
314 expect(disabled.targets).toEqual(registered.targets);
315 expect(disabled.credentials).toEqual(registered.credentials);
316 expect(
317 setConnectionEnabledInCatalog(disabled, ENVIRONMENT_ID, false).disabledEnvironmentIds,
318 ).toEqual([ENVIRONMENT_ID]);
319​
320 expect(
321 setConnectionEnabledInCatalog(disabled, ENVIRONMENT_ID, true).disabledEnvironmentIds,
322 ).toEqual([]);
323 expect(
324 registerConnectionInCatalog(
325 disabled,
326 new BearerConnectionRegistration({
327 target: BEARER_TARGET,
328 profile: BEARER_PROFILE,
329 credential: BEARER_CREDENTIAL,
330 }),
331 ).disabledEnvironmentIds,
332 ).toEqual([ENVIRONMENT_ID]);
333 expect(removeConnectionFromCatalog(disabled, ENVIRONMENT_ID).disabledEnvironmentIds).toEqual(
334 [],
335 );
336 });
337​
338 it("persists the normalized SSH profile and its paired token beside its target", () => {
339 const target = new SshConnectionTarget({
340 environmentId: ENVIRONMENT_ID,
341 label: "SSH",
342 connectionId: "ssh-1",
343 });
344 const profile = new SshConnectionProfile({
345 connectionId: target.connectionId,
346 environmentId: target.environmentId,
347 label: target.label,
348 target: {
349 alias: "devbox",
350 hostname: "devbox.example.test",
351 username: "developer",
352 port: 22,
353 },
354 });
355 const document = registerConnectionInCatalog(
356 EMPTY_CONNECTION_CATALOG_DOCUMENT,
357 new SshConnectionRegistration({ target, profile, credential: BEARER_CREDENTIAL }),
358 );
359​
360 expect(document.targets).toEqual([target]);
361 expect(document.profiles).toEqual([profile]);
362 expect(document.credentials).toEqual([
363 { connectionId: target.connectionId, credential: BEARER_CREDENTIAL },
364 ]);
365 // Removing the route forgets its token too.
366 expect(removeConnectionFromCatalog(document, ENVIRONMENT_ID).credentials).toEqual([]);
367 });
368​
369 it("keeps every route of an environment and drops only a removed route's records", () => {
370 const withBearer = registerConnectionInCatalog(
371 { ...EMPTY_CONNECTION_CATALOG_DOCUMENT, remoteDpopTokens: [REMOTE_TOKEN] },
372 new BearerConnectionRegistration({
373 target: BEARER_TARGET,
374 profile: BEARER_PROFILE,
375 credential: BEARER_CREDENTIAL,
376 }),
377 );
378 const withRelay = registerConnectionInCatalog(
379 withBearer,
380 new RelayConnectionRegistration({ target: RELAY_TARGET }),
381 [BEARER_TARGET, RELAY_TARGET],
382 );
383 expect(catalogRoutes(withRelay, ENVIRONMENT_ID)).toEqual([BEARER_TARGET, RELAY_TARGET]);
384 expect(withRelay.credentials).toHaveLength(1);
385​
386 const relayOnly = setRoutesInCatalog(withRelay, ENVIRONMENT_ID, [RELAY_TARGET]);
387 expect(relayOnly.targets).toEqual([RELAY_TARGET]);
388 expect(relayOnly.profiles).toEqual([]);
389 expect(relayOnly.credentials).toEqual([]);
390 expect(relayOnly.remoteDpopTokens).toEqual([REMOTE_TOKEN]);
391​
392 const bearerOnly = setRoutesInCatalog(withRelay, ENVIRONMENT_ID, [BEARER_TARGET]);
393 expect(bearerOnly.credentials).toHaveLength(1);
394 expect(bearerOnly.remoteDpopTokens).toEqual([]);
395 });
396​
397 it("keeps an environment's position in the catalog when its routes change", () => {
398 const other = new RelayConnectionTarget({
399 environmentId: EnvironmentId.make("environment-2"),
400 label: "Other",
401 });
402 const document = {
403 ...EMPTY_CONNECTION_CATALOG_DOCUMENT,
404 targets: [RELAY_TARGET, other],
405 };
406 expect(
407 setRoutesInCatalog(document, ENVIRONMENT_ID, [BEARER_TARGET, RELAY_TARGET]).targets,
408 ).toEqual([BEARER_TARGET, RELAY_TARGET, other]);
409 });
410});