| ... | ... | @@ -1,16 +1,18 @@ |
| 1 | | // @effect-diagnostics nodeBuiltinImport:off |
| 2 | | import * as NodePath from "node:path"; |
| 3 | 1 | import * as NodeServices from "@effect/platform-node/NodeServices"; |
| 4 | 2 | import { expect, it } from "@effect/vitest"; |
| 5 | 3 | import * as Effect from "effect/Effect"; |
| 6 | | import * as Exit from "effect/Exit"; |
| 7 | 4 | import * as FileSystem from "effect/FileSystem"; |
| 8 | 5 | import * as Layer from "effect/Layer"; |
| 9 | 6 | import * as PlatformError from "effect/PlatformError"; |
| 7 | import * as Schema from "effect/Schema"; |
| 10 | 8 | |
| 11 | 9 | import * as ServerConfig from "../config.ts"; |
| 12 | 10 | import * as ServerEnvironment from "./ServerEnvironment.ts"; |
| 13 | 11 | |
| 12 | const isServerEnvironmentIdPersistenceError = Schema.is( |
| 13 | ServerEnvironment.ServerEnvironmentIdPersistenceError, |
| 14 | ); |
| 15 | |
| 14 | 16 | const makeServerEnvironmentLayer = (baseDir: string) => |
| 15 | 17 | ServerEnvironment.layer.pipe(Layer.provide(ServerConfig.layerTest(process.cwd(), baseDir))); |
| 16 | 18 | |
| ... | ... | @@ -68,62 +70,63 @@ it.layer(NodeServices.layer)("ServerEnvironmentLive", (it) => { |
| 68 | 70 | }), |
| 69 | 71 | ); |
| 70 | 72 | |
| 71 | | it.effect("fails instead of overwriting a persisted id when reading the file errors", () => |
| 73 | it.effect("structures persisted environment id filesystem failures", () => |
| 72 | 74 | Effect.gen(function* () { |
| 73 | 75 | const fileSystem = yield* FileSystem.FileSystem; |
| 74 | 76 | const baseDir = yield* fileSystem.makeTempDirectoryScoped({ |
| 75 | | prefix: "t3-server-environment-read-error-test-", |
| 77 | prefix: "t3-server-environment-error-test-", |
| 76 | 78 | }); |
| 77 | 79 | const serverConfig = yield* makeServerConfig(baseDir); |
| 78 | 80 | const environmentIdPath = serverConfig.environmentIdPath; |
| 79 | | yield* fileSystem.makeDirectory(NodePath.dirname(environmentIdPath), { recursive: true }); |
| 80 | | yield* fileSystem.writeFileString(environmentIdPath, "persisted-environment-id\n"); |
| 81 | | const writeAttempts: string[] = []; |
| 82 | | const failingFileSystemLayer = FileSystem.layerNoop({ |
| 83 | | exists: (path) => Effect.succeed(path === environmentIdPath), |
| 84 | | readFileString: (path) => |
| 85 | | path === environmentIdPath |
| 86 | | ? Effect.fail( |
| 87 | | PlatformError.systemError({ |
| 88 | | _tag: "PermissionDenied", |
| 89 | | module: "FileSystem", |
| 90 | | method: "readFileString", |
| 91 | | description: "permission denied", |
| 92 | | pathOrDescriptor: path, |
| 93 | | }), |
| 94 | | ) |
| 95 | | : Effect.fail( |
| 96 | | PlatformError.systemError({ |
| 97 | | _tag: "NotFound", |
| 98 | | module: "FileSystem", |
| 99 | | method: "readFileString", |
| 100 | | description: "not found", |
| 101 | | pathOrDescriptor: path, |
| 102 | | }), |
| 103 | | ), |
| 104 | | writeFileString: (path) => { |
| 105 | | writeAttempts.push(path); |
| 106 | | return Effect.void; |
| 107 | | }, |
| 108 | | }); |
| 81 | const methodByOperation = { |
| 82 | check: "exists", |
| 83 | read: "readFileString", |
| 84 | write: "writeFileString", |
| 85 | } as const; |
| 109 | 86 | |
| 110 | | const exit = yield* Effect.gen(function* () { |
| 111 | | const serverEnvironment = yield* ServerEnvironment.ServerEnvironment; |
| 112 | | return yield* serverEnvironment.getDescriptor; |
| 113 | | }).pipe( |
| 114 | | Effect.provide( |
| 115 | | ServerEnvironment.layer.pipe( |
| 116 | | Layer.provide(Layer.merge(ServerConfig.layer(serverConfig), failingFileSystemLayer)), |
| 87 | for (const operation of ["check", "read", "write"] as const) { |
| 88 | const writeAttempts: string[] = []; |
| 89 | const cause = PlatformError.systemError({ |
| 90 | _tag: "PermissionDenied", |
| 91 | module: "FileSystem", |
| 92 | method: methodByOperation[operation], |
| 93 | description: "permission denied", |
| 94 | pathOrDescriptor: environmentIdPath, |
| 95 | }); |
| 96 | const failingFileSystemLayer = FileSystem.layerNoop({ |
| 97 | exists: () => |
| 98 | operation === "check" ? Effect.fail(cause) : Effect.succeed(operation === "read"), |
| 99 | readFileString: () => Effect.fail(cause), |
| 100 | writeFileString: (path) => { |
| 101 | writeAttempts.push(path); |
| 102 | return Effect.fail(cause); |
| 103 | }, |
| 104 | }); |
| 105 | |
| 106 | const error = yield* Effect.gen(function* () { |
| 107 | const serverEnvironment = yield* ServerEnvironment.ServerEnvironment; |
| 108 | return yield* serverEnvironment.getDescriptor; |
| 109 | }).pipe( |
| 110 | Effect.provide( |
| 111 | ServerEnvironment.layer.pipe( |
| 112 | Layer.provide(Layer.merge(ServerConfig.layer(serverConfig), failingFileSystemLayer)), |
| 113 | ), |
| 117 | 114 | ), |
| 118 | | ), |
| 119 | | Effect.exit, |
| 120 | | ); |
| 115 | Effect.flip, |
| 116 | ); |
| 121 | 117 | |
| 122 | | expect(Exit.isFailure(exit)).toBe(true); |
| 123 | | expect(writeAttempts).toEqual([]); |
| 124 | | expect(yield* fileSystem.readFileString(environmentIdPath)).toBe( |
| 125 | | "persisted-environment-id\n", |
| 126 | | ); |
| 118 | expect(isServerEnvironmentIdPersistenceError(error)).toBe(true); |
| 119 | if (!isServerEnvironmentIdPersistenceError(error)) { |
| 120 | throw error; |
| 121 | } |
| 122 | expect(error.operation).toBe(operation); |
| 123 | expect(error.environmentIdPath).toBe(environmentIdPath); |
| 124 | expect(error.cause).toBe(cause); |
| 125 | expect(error.message).toBe( |
| 126 | `Server environment ID ${operation} failed at '${environmentIdPath}'.`, |
| 127 | ); |
| 128 | expect(writeAttempts).toEqual(operation === "write" ? [environmentIdPath] : []); |
| 129 | } |
| 127 | 130 | }), |
| 128 | 131 | ); |
| 129 | 132 | }); |