| ... | @@ -1,16 +1,18 @@ | ... | @@ -1,16 +1,18 @@ |
| 1 | // @effect-diagnostics nodeBuiltinImport:off | | |
| 2 | import * as NodePath from "node:path"; | | |
| 3 | import * as NodeServices from "@effect/platform-node/NodeServices"; | 1 | import * as NodeServices from "@effect/platform-node/NodeServices"; |
| 4 | import { expect, it } from "@effect/vitest"; | 2 | import { expect, it } from "@effect/vitest"; |
| 5 | import * as Effect from "effect/Effect"; | 3 | import * as Effect from "effect/Effect"; |
| 6 | import * as Exit from "effect/Exit"; | | |
| 7 | import * as FileSystem from "effect/FileSystem"; | 4 | import * as FileSystem from "effect/FileSystem"; |
| 8 | import * as Layer from "effect/Layer"; | 5 | import * as Layer from "effect/Layer"; |
| 9 | import * as PlatformError from "effect/PlatformError"; | 6 | import * as PlatformError from "effect/PlatformError"; |
| | 7 | import * as Schema from "effect/Schema"; |
| 10 | | 8 | |
| 11 | import * as ServerConfig from "../config.ts"; | 9 | import * as ServerConfig from "../config.ts"; |
| 12 | import * as ServerEnvironment from "./ServerEnvironment.ts"; | 10 | import * as ServerEnvironment from "./ServerEnvironment.ts"; |
| 13 | | 11 | |
| | 12 | const isServerEnvironmentIdPersistenceError = Schema.is( |
| | 13 | ServerEnvironment.ServerEnvironmentIdPersistenceError, |
| | 14 | ); |
| | 15 | |
| 14 | const makeServerEnvironmentLayer = (baseDir: string) => | 16 | const makeServerEnvironmentLayer = (baseDir: string) => |
| 15 | ServerEnvironment.layer.pipe(Layer.provide(ServerConfig.layerTest(process.cwd(), baseDir))); | 17 | ServerEnvironment.layer.pipe(Layer.provide(ServerConfig.layerTest(process.cwd(), baseDir))); |
| 16 | | 18 | |
| ... | @@ -68,62 +70,63 @@ it.layer(NodeServices.layer)("ServerEnvironmentLive", (it) => { | ... | @@ -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 | Effect.gen(function* () { | 74 | Effect.gen(function* () { |
| 73 | const fileSystem = yield* FileSystem.FileSystem; | 75 | const fileSystem = yield* FileSystem.FileSystem; |
| 74 | const baseDir = yield* fileSystem.makeTempDirectoryScoped({ | 76 | const baseDir = yield* fileSystem.makeTempDirectoryScoped({ |
| 75 | prefix: "t3-server-environment-read-error-test-", | 77 | prefix: "t3-server-environment-error-test-", |
| 76 | }); | 78 | }); |
| 77 | const serverConfig = yield* makeServerConfig(baseDir); | 79 | const serverConfig = yield* makeServerConfig(baseDir); |
| 78 | const environmentIdPath = serverConfig.environmentIdPath; | 80 | const environmentIdPath = serverConfig.environmentIdPath; |
| 79 | yield* fileSystem.makeDirectory(NodePath.dirname(environmentIdPath), { recursive: true }); | 81 | const methodByOperation = { |
| 80 | yield* fileSystem.writeFileString(environmentIdPath, "persisted-environment-id\n"); | 82 | check: "exists", |
| 81 | const writeAttempts: string[] = []; | 83 | read: "readFileString", |
| 82 | const failingFileSystemLayer = FileSystem.layerNoop({ | 84 | write: "writeFileString", |
| 83 | exists: (path) => Effect.succeed(path === environmentIdPath), | 85 | } as const; |
| 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 | }); | | |
| 109 | | 86 | |
| 110 | const exit = yield* Effect.gen(function* () { | 87 | for (const operation of ["check", "read", "write"] as const) { |
| 111 | const serverEnvironment = yield* ServerEnvironment.ServerEnvironment; | 88 | const writeAttempts: string[] = []; |
| 112 | return yield* serverEnvironment.getDescriptor; | 89 | const cause = PlatformError.systemError({ |
| 113 | }).pipe( | 90 | _tag: "PermissionDenied", |
| 114 | Effect.provide( | 91 | module: "FileSystem", |
| 115 | ServerEnvironment.layer.pipe( | 92 | method: methodByOperation[operation], |
| 116 | Layer.provide(Layer.merge(ServerConfig.layer(serverConfig), failingFileSystemLayer)), | 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 | ), | 115 | Effect.flip, |
| 119 | Effect.exit, | 116 | ); |
| 120 | ); | | |
| 121 | | 117 | |
| 122 | expect(Exit.isFailure(exit)).toBe(true); | 118 | expect(isServerEnvironmentIdPersistenceError(error)).toBe(true); |
| 123 | expect(writeAttempts).toEqual([]); | 119 | if (!isServerEnvironmentIdPersistenceError(error)) { |
| 124 | expect(yield* fileSystem.readFileString(environmentIdPath)).toBe( | 120 | throw error; |
| 125 | "persisted-environment-id\n", | 121 | } |
| 126 | ); | 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 | }); |