Skip to content
Source
Sign inSign up

ServerSecretStore.test.ts

174 lines6.6 KB

1import * as NodeCrypto from "@effect/platform-node/NodeCrypto";
2import * as NodeServices from "@effect/platform-node/NodeServices";
3import { assert, it } from "@effect/vitest";
4import * as Deferred from "effect/Deferred";
5import * as Effect from "effect/Effect";
6import * as FileSystem from "effect/FileSystem";
7import * as Layer from "effect/Layer";
8import * as Option from "effect/Option";
9import * as Ref from "effect/Ref";
10import * as PlatformError from "effect/PlatformError";
11​
12import * as ServerConfig from "../config.ts";
13import * as ServerSecretStore from "./ServerSecretStore.ts";
14​
15const layerServerConfig = () =>
16 ServerConfig.layerTest(process.cwd(), { prefix: "t3-secret-store-test-" });
17​
18const layerServerSecretStore = () => Layer.provide(ServerSecretStore.layer, layerServerConfig());
19​
20const layerPermissionDeniedFileSystem = Layer.effect(
21 FileSystem.FileSystem,
22 Effect.gen(function* () {
23 const fileSystem = yield* FileSystem.FileSystem;
24​
25 return {
26 ...fileSystem,
27 readFile: (path) =>
28 Effect.fail(
29 PlatformError.systemError({
30 _tag: "PermissionDenied",
31 module: "FileSystem",
32 method: "readFile",
33 pathOrDescriptor: path,
34 description: "Permission denied while reading secret file.",
35 }),
36 ),
37 } satisfies FileSystem.FileSystem;
38 }),
39).pipe(Layer.provide(Layer.mergeAll(NodeServices.layer, NodeCrypto.layer)));
40​
41const layerPermissionDeniedSecretStore = () =>
42 ServerSecretStore.layer.pipe(
43 Layer.provide(layerServerConfig()),
44 Layer.provideMerge(layerPermissionDeniedFileSystem),
45 );
46​
47const layerRenameFailureFileSystem = Layer.effect(
48 FileSystem.FileSystem,
49 Effect.gen(function* () {
50 const fileSystem = yield* FileSystem.FileSystem;
51​
52 return {
53 ...fileSystem,
54 rename: (from, to) =>
55 Effect.fail(
56 PlatformError.systemError({
57 _tag: "PermissionDenied",
58 module: "FileSystem",
59 method: "rename",
60 pathOrDescriptor: `${String(from)} -> ${String(to)}`,
61 description: "Permission denied while persisting secret file.",
62 }),
63 ),
64 } satisfies FileSystem.FileSystem;
65 }),
66).pipe(Layer.provide(Layer.mergeAll(NodeServices.layer, NodeCrypto.layer)));
67​
68const layerRenameFailureSecretStore = () =>
69 ServerSecretStore.layer.pipe(
70 Layer.provide(layerServerConfig()),
71 Layer.provideMerge(layerRenameFailureFileSystem),
72 );
73​
74const layerConcurrentReadMissFileSystem = Layer.effect(
75 FileSystem.FileSystem,
76 Effect.gen(function* () {
77 const fileSystem = yield* FileSystem.FileSystem;
78 const readCountRef = yield* Ref.make(0);
79 const readBarrier = yield* Deferred.make<void>();
80​
81 return {
82 ...fileSystem,
83 readFile: (path) =>
84 /[\\/]session-signing-key\.bin$/.test(String(path))
85 ? Ref.updateAndGet(readCountRef, (count) => count + 1).pipe(
86 Effect.flatMap((count) => {
87 if (count > 2) {
88 return fileSystem.readFile(path);
89 }
90 return Effect.gen(function* () {
91 if (count === 2) {
92 yield* Deferred.succeed(readBarrier, void 0);
93 }
94 yield* Deferred.await(readBarrier);
95 return yield* PlatformError.systemError({
96 _tag: "NotFound",
97 module: "FileSystem",
98 method: "readFile",
99 pathOrDescriptor: String(path),
100 description: "Secret file does not exist yet.",
101 });
102 });
103 }),
104 )
105 : fileSystem.readFile(path),
106 } satisfies FileSystem.FileSystem;
107 }),
108).pipe(Layer.provide(Layer.mergeAll(NodeServices.layer, NodeCrypto.layer)));
109​
110const layerConcurrentCreateSecretStore = () =>
111 ServerSecretStore.layer.pipe(
112 Layer.provide(layerServerConfig()),
113 Layer.provideMerge(layerConcurrentReadMissFileSystem),
114 );
115​
116it.layer(Layer.mergeAll(NodeServices.layer, NodeCrypto.layer))("ServerSecretStore.layer", (it) => {
117 it.effect("reuses an existing secret instead of regenerating it", () =>
118 Effect.gen(function* () {
119 const secretStore = yield* ServerSecretStore.ServerSecretStore;
120​
121 const first = yield* secretStore.getOrCreateRandom("session-signing-key", 32);
122 const second = yield* secretStore.getOrCreateRandom("session-signing-key", 32);
123​
124 assert.deepEqual(Array.from(second), Array.from(first));
125 }).pipe(Effect.provide(layerServerSecretStore())),
126 );
127​
128 it.effect("returns the persisted secret when concurrent creators race", () =>
129 Effect.gen(function* () {
130 const secretStore = yield* ServerSecretStore.ServerSecretStore;
131​
132 const [first, second] = yield* Effect.all(
133 [
134 secretStore.getOrCreateRandom("session-signing-key", 32),
135 secretStore.getOrCreateRandom("session-signing-key", 32),
136 ],
137 { concurrency: "unbounded" },
138 );
139 const persisted = yield* secretStore.get("session-signing-key");
140 const persistedBytes = Option.getOrThrow(persisted);
141​
142 assert.deepEqual(Array.from(first), Array.from(persistedBytes));
143 assert.deepEqual(Array.from(second), Array.from(persistedBytes));
144 }).pipe(Effect.provide(layerConcurrentCreateSecretStore())),
145 );
146​
147 it.effect("propagates read failures other than missing-file errors", () =>
148 Effect.gen(function* () {
149 const secretStore = yield* ServerSecretStore.ServerSecretStore;
150​
151 const error = yield* Effect.flip(secretStore.getOrCreateRandom("session-signing-key", 32));
152​
153 assert.instanceOf(error, ServerSecretStore.SecretStoreReadError);
154 assert.include(error.message, "Failed to read secret session-signing-key.");
155 assert.instanceOf(error.cause, PlatformError.PlatformError);
156 assert.equal((error.cause as PlatformError.PlatformError).reason._tag, "PermissionDenied");
157 }).pipe(Effect.provide(layerPermissionDeniedSecretStore())),
158 );
159​
160 it.effect("propagates write failures instead of treating them as success", () =>
161 Effect.gen(function* () {
162 const secretStore = yield* ServerSecretStore.ServerSecretStore;
163​
164 const error = yield* Effect.flip(
165 secretStore.set("session-signing-key", Uint8Array.from([1, 2, 3])),
166 );
167​
168 assert.instanceOf(error, ServerSecretStore.SecretStorePersistError);
169 assert.include(error.message, "Failed to persist secret session-signing-key.");
170 assert.instanceOf(error.cause, PlatformError.PlatformError);
171 assert.equal((error.cause as PlatformError.PlatformError).reason._tag, "PermissionDenied");
172 }).pipe(Effect.provide(layerRenameFailureSecretStore())),
173 );
174});