History

[codex] Structure workspace file system errors (#3274)

Julius Marminge committed 4 months ago

Commit cdfecb31 parent f4ef3568 files · +707 −119Browse files

Showing the changes to apps/server/src/workspace/WorkspaceFileSystem.test.ts (1 of 8 files). Show all

Unified · Split

apps/server/src/workspace/WorkspaceFileSystem.test.ts+81−6
@@ -104,14 +104,89 @@ it.layer(TestLayer, { excludeTestServices: true })("WorkspaceFileSystemLive", (i · 103 unmodified lines
104104 const error = yield* workspaceFileSystem
105105 .readFile({ cwd, relativePath: "linked-secret.txt" })
106106 .pipe(Effect.flip);
107+ const resolvedWorkspaceRoot = yield* fileSystem.realPath(cwd);
108+ const resolvedPath = yield* fileSystem.realPath(path.join(outsideDir, "secret.txt"));
107109
108− expect(error.message).toBe(
109− `Workspace file operation 'workspaceFileSystem.readFile' failed for 'linked-secret.txt' in '${cwd}'.`,
110− );
110+ expect(error).toBeInstanceOf(WorkspaceFileSystem.WorkspaceFilePathEscapeError);
111+ expect(error).toMatchObject({
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+ });
111188 expect(error.cause).toBeInstanceOf(Error);
112− expect((error.cause as Error).message).toBe(
113− "Workspace file path resolves outside the project root.",
114− );
189+ expect((error.cause as NodeJS.ErrnoException).code).toBe("ENOENT");
115190 }),
116191 );
117192 });