Showing the changes to apps/server/src/workspace/WorkspaceFileSystem.test.ts (1 of 8 files). Show all
Unified · Split
| @@ -104,14 +104,89 @@ it.layer(TestLayer, { excludeTestServices: true })("WorkspaceFileSystemLive", (i · 103 unmodified lines | |||
| 104 | const error = yield* workspaceFileSystem | 104 | const error = yield* workspaceFileSystem |
| 105 | .readFile({ cwd, relativePath: "linked-secret.txt" }) | 105 | .readFile({ cwd, relativePath: "linked-secret.txt" }) |
| 106 | .pipe(Effect.flip); | 106 | .pipe(Effect.flip); |
| 107 | const resolvedWorkspaceRoot = yield* fileSystem.realPath(cwd); | ||
| 108 | const resolvedPath = yield* fileSystem.realPath(path.join(outsideDir, "secret.txt")); | ||
| 107 | 109 | ||
| 108 | expect(error.message).toBe( | 110 | expect(error).toBeInstanceOf(WorkspaceFileSystem.WorkspaceFilePathEscapeError); |
| 109 | `Workspace file operation 'workspaceFileSystem.readFile' failed for 'linked-secret.txt' in '${cwd}'.`, | 111 | expect(error).toMatchObject({ |
| 110 | ); | 112 | workspaceRoot: cwd, |
| 113 | relativePath: "linked-secret.txt", | ||
| 114 | resolvedWorkspaceRoot, | ||
| 115 | resolvedPath, | ||
| 116 | }); | ||
| 117 | expect("cause" in error).toBe(false); | ||
| 118 | }), | ||
| 119 | ); | ||
| 120 | |||
| 121 | it.effect("rejects directories without manufacturing an I/O cause", () => | ||
| 122 | Effect.gen(function* () { | ||
| 123 | const workspaceFileSystem = yield* WorkspaceFileSystem.WorkspaceFileSystem; | ||
| 124 | const fileSystem = yield* FileSystem.FileSystem; | ||
| 125 | const path = yield* Path.Path; | ||
| 126 | const cwd = yield* makeTempDir; | ||
| 127 | yield* fileSystem.makeDirectory(path.join(cwd, "src")); | ||
| 128 | |||
| 129 | const error = yield* workspaceFileSystem | ||
| 130 | .readFile({ cwd, relativePath: "src" }) | ||
| 131 | .pipe(Effect.flip); | ||
| 132 | const resolvedPath = yield* fileSystem.realPath(path.join(cwd, "src")); | ||
| 133 | |||
| 134 | expect(error).toBeInstanceOf(WorkspaceFileSystem.WorkspacePathNotFileError); | ||
| 135 | expect(error).toMatchObject({ | ||
| 136 | workspaceRoot: cwd, | ||
| 137 | relativePath: "src", | ||
| 138 | resolvedPath, | ||
| 139 | }); | ||
| 140 | expect("cause" in error).toBe(false); | ||
| 141 | }), | ||
| 142 | ); | ||
| 143 | |||
| 144 | it.effect("rejects binary files without leaking their contents into the error", () => | ||
| 145 | Effect.gen(function* () { | ||
| 146 | const workspaceFileSystem = yield* WorkspaceFileSystem.WorkspaceFileSystem; | ||
| 147 | const fileSystem = yield* FileSystem.FileSystem; | ||
| 148 | const path = yield* Path.Path; | ||
| 149 | const cwd = yield* makeTempDir; | ||
| 150 | const absolutePath = path.join(cwd, "asset.bin"); | ||
| 151 | yield* fileSystem.writeFile(absolutePath, Uint8Array.from([0x61, 0, 0x62])); | ||
| 152 | |||
| 153 | const error = yield* workspaceFileSystem | ||
| 154 | .readFile({ cwd, relativePath: "asset.bin" }) | ||
| 155 | .pipe(Effect.flip); | ||
| 156 | const resolvedPath = yield* fileSystem.realPath(absolutePath); | ||
| 157 | |||
| 158 | expect(error).toBeInstanceOf(WorkspaceFileSystem.WorkspaceBinaryFileError); | ||
| 159 | expect(error).toMatchObject({ | ||
| 160 | workspaceRoot: cwd, | ||
| 161 | relativePath: "asset.bin", | ||
| 162 | resolvedPath, | ||
| 163 | }); | ||
| 164 | expect("cause" in error).toBe(false); | ||
| 165 | expect("contents" in error).toBe(false); | ||
| 166 | }), | ||
| 167 | ); | ||
| 168 | |||
| 169 | it.effect("preserves the real cause and path for I/O failures", () => | ||
| 170 | Effect.gen(function* () { | ||
| 171 | const workspaceFileSystem = yield* WorkspaceFileSystem.WorkspaceFileSystem; | ||
| 172 | const path = yield* Path.Path; | ||
| 173 | const cwd = yield* makeTempDir; | ||
| 174 | const resolvedPath = path.join(cwd, "missing.txt"); | ||
| 175 | |||
| 176 | const error = yield* workspaceFileSystem | ||
| 177 | .readFile({ cwd, relativePath: "missing.txt" }) | ||
| 178 | .pipe(Effect.flip); | ||
| 179 | |||
| 180 | expect(error).toBeInstanceOf(WorkspaceFileSystem.WorkspaceFileSystemOperationError); | ||
| 181 | expect(error).toMatchObject({ | ||
| 182 | workspaceRoot: cwd, | ||
| 183 | relativePath: "missing.txt", | ||
| 184 | resolvedPath, | ||
| 185 | operationPath: resolvedPath, | ||
| 186 | operation: "realpath-target", | ||
| 187 | }); | ||
| 111 | expect(error.cause).toBeInstanceOf(Error); | 188 | expect(error.cause).toBeInstanceOf(Error); |
| 112 | expect((error.cause as Error).message).toBe( | 189 | expect((error.cause as NodeJS.ErrnoException).code).toBe("ENOENT"); |
| 113 | "Workspace file path resolves outside the project root.", | ||
| 114 | ); | ||
| 115 | }), | 190 | }), |
| 116 | ); | 191 | ); |
| 117 | }); | 192 | }); |