History

fix(web): Remove saved environments atomically (#2917)

Mike Olson committed 4 months ago

Commit 1fcc57a1 parent e95b57d2 files · +52 −0Browse files

Unified · Split

2 files
  1. apps/desktop/src/settings/DesktopSavedEnvironments.test.ts +20 −0
  2. apps/desktop/src/settings/DesktopSavedEnvironments.ts +32 −0
apps/desktop/src/settings/DesktopSavedEnvironments.test.ts+20−0
@@ -272,6 +272,26 @@ describe("DesktopSavedEnvironments", () => { · 271 unmodified lines
272 ),272 ),
273 );273 );
274 274
275 it.effect("removes saved environment metadata and its embedded secret atomically", () =>
276 withSavedEnvironments(
277 Effect.gen(function* () {
278 const savedEnvironments = yield* DesktopSavedEnvironments.DesktopSavedEnvironments;
279 yield* savedEnvironments.setRegistry([savedRegistryRecord]);
280 yield* savedEnvironments.setSecret({
281 environmentId: savedRegistryRecord.environmentId,
282 secret: "bearer-token",
283 });
284
285 yield* savedEnvironments.removeEnvironment(savedRegistryRecord.environmentId);
286
287 assert.deepEqual(yield* savedEnvironments.getRegistry, []);
288 assert.isTrue(
289 Option.isNone(yield* savedEnvironments.getSecret(savedRegistryRecord.environmentId)),
290 );
291 }),
292 ),
293 );
294
275 it.effect("treats empty saved environment documents as empty", () =>295 it.effect("treats empty saved environment documents as empty", () =>
276 withSavedEnvironments(296 withSavedEnvironments(
277 Effect.gen(function* () {297 Effect.gen(function* () {
apps/desktop/src/settings/DesktopSavedEnvironments.ts+32−0
@@ -121,6 +121,9 @@ export interface DesktopSavedEnvironmentsShape { · 120 unmodified lines
121 readonly setRegistry: (121 readonly setRegistry: (
122 records: readonly PersistedSavedEnvironmentRecord[],122 records: readonly PersistedSavedEnvironmentRecord[],
123 ) => Effect.Effect<void, DesktopSavedEnvironmentsWriteError>;123 ) => Effect.Effect<void, DesktopSavedEnvironmentsWriteError>;
124 readonly removeEnvironment: (
125 environmentId: string,
126 ) => Effect.Effect<void, DesktopSavedEnvironmentsWriteError>;
124 readonly getSecret: (127 readonly getSecret: (
125 environmentId: string,128 environmentId: string,
126 ) => Effect.Effect<Option.Option<string>, DesktopSavedEnvironmentsGetSecretError>;129 ) => Effect.Effect<Option.Option<string>, DesktopSavedEnvironmentsGetSecretError>;
@@ -293,6 +296,23 @@ export const layer = Layer.effect( · 166 unmodified lines
293 ).pipe(Effect.mapError((cause) => new DesktopSavedEnvironmentsWriteError({ cause })));296 ).pipe(Effect.mapError((cause) => new DesktopSavedEnvironmentsWriteError({ cause })));
294 yield* writeDocument(preserveExistingSecrets(currentDocument, records));297 yield* writeDocument(preserveExistingSecrets(currentDocument, records));
295 }),298 }),
299 removeEnvironment: Effect.fn("desktop.savedEnvironments.removeEnvironment")(
300 function* (environmentId) {
301 yield* Effect.annotateCurrentSpan({ environmentId });
302 const document = yield* readRegistryDocument(
303 fileSystem,
304 environment.savedEnvironmentRegistryPath,
305 ).pipe(Effect.mapError((cause) => new DesktopSavedEnvironmentsWriteError({ cause })));
306 if (!document.records.some((record) => record.environmentId === environmentId)) {
307 return;
308 }
309
310 yield* writeDocument({
311 version: document.version,
312 records: document.records.filter((record) => record.environmentId !== environmentId),
313 });
314 },
315 ),
296 getSecret: Effect.fn("desktop.savedEnvironments.getSecret")(function* (environmentId) {316 getSecret: Effect.fn("desktop.savedEnvironments.getSecret")(function* (environmentId) {
297 yield* Effect.annotateCurrentSpan({ environmentId });317 yield* Effect.annotateCurrentSpan({ environmentId });
298 const document = yield* readRegistryDocument(318 const document = yield* readRegistryDocument(
@@ -385,6 +405,18 @@ export const layerTest = (input?: { · 86 unmodified lines
385 return DesktopSavedEnvironments.of({405 return DesktopSavedEnvironments.of({
386 getRegistry: Ref.get(recordsRef),406 getRegistry: Ref.get(recordsRef),
387 setRegistry: (records) => Ref.set(recordsRef, records),407 setRegistry: (records) => Ref.set(recordsRef, records),
408 removeEnvironment: (environmentId) =>
409 Ref.update(recordsRef, (records) =>
410 records.filter((record) => record.environmentId !== environmentId),
411 ).pipe(
412 Effect.andThen(
413 Ref.update(secretsRef, (secrets) => {
414 const nextSecrets = new Map(secrets);
415 nextSecrets.delete(environmentId);
416 return nextSecrets;
417 }),
418 ),
419 ),
388 getSecret: (environmentId) =>420 getSecret: (environmentId) =>
389 Ref.get(secretsRef).pipe(421 Ref.get(secretsRef).pipe(
390 Effect.map((secrets) => Option.fromNullishOr(secrets.get(environmentId))),422 Effect.map((secrets) => Option.fromNullishOr(secrets.get(environmentId))),