History

Use Effect FileSystem for workspace reads

Cursor Agent committed 4 months ago

Commit 05760161 parent 97e5cd32 files · +115 −59Browse files

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

Unified · Split

apps/server/src/workspace/WorkspaceFileSystem.test.ts+76−19
@@ -1,5 +1,5 @@
11 import * as NodeServices from "@effect/platform-node/NodeServices";
2−import { it, describe, expect } from "@effect/vitest";
2+import { assert, it, describe } from "@effect/vitest";
33 import * as Effect from "effect/Effect";
44 import * as FileSystem from "effect/FileSystem";
55 import * as Layer from "effect/Layer";
@@ -12,6 +12,8 @@ import * as WorkspaceEntries from "./WorkspaceEntries.ts"; · 6 unmodified lines
1212 import * as WorkspaceFileSystem from "./WorkspaceFileSystem.ts";
1313 import * as WorkspacePaths from "./WorkspacePaths.ts";
1414
15+const PROJECT_READ_FILE_MAX_BYTES = 1024 * 1024;
16+
1517 const ProjectLayer = WorkspaceFileSystem.layer.pipe(
1618 Layer.provide(WorkspacePaths.layer),
1719 Layer.provide(WorkspaceEntries.layer.pipe(Layer.provide(WorkspacePaths.layer))),
@@ -51,6 +53,20 @@ const writeTextFile = Effect.fn("writeTextFile")(function* ( · 33 unmodified lines
5153 yield* fileSystem.writeFileString(absolutePath, contents).pipe(Effect.orDie);
5254 });
5355
56+const writeBinaryFile = Effect.fn("writeBinaryFile")(function* (
57+ cwd: string,
58+ relativePath: string,
59+ contents: Uint8Array,
60+) {
61+ const fileSystem = yield* FileSystem.FileSystem;
62+ const path = yield* Path.Path;
63+ const absolutePath = path.join(cwd, relativePath);
64+ yield* fileSystem
65+ .makeDirectory(path.dirname(absolutePath), { recursive: true })
66+ .pipe(Effect.orDie);
67+ yield* fileSystem.writeFile(absolutePath, contents).pipe(Effect.orDie);
68+});
69+
5470 it.layer(TestLayer, { excludeTestServices: true })("WorkspaceFileSystemLive", (it) => {
5571 describe("readFile", () => {
5672 it.effect("reads UTF-8 files relative to the workspace root", () =>
@@ -64,7 +80,7 @@ it.layer(TestLayer, { excludeTestServices: true })("WorkspaceFileSystemLive", (i · 7 unmodified lines
6480 relativePath: "src/index.ts",
6581 });
6682
67− expect(result).toEqual({
83+ assert.deepStrictEqual(result, {
6884 relativePath: "src/index.ts",
6985 contents: "export const answer = 42;\n",
7086 byteLength: 26,
@@ -82,9 +98,47 @@ it.layer(TestLayer, { excludeTestServices: true })("WorkspaceFileSystemLive", (i · 11 unmodified lines
8298 .readFile({ cwd, relativePath: "../escape.md" })
8399 .pipe(Effect.flip);
84100
85− expect(error.message).toContain(
86− "Workspace file path must be relative to the project root: ../escape.md",
101+ assert.match(
102+ error.message,
103+ /Workspace file path must be relative to the project root: \.\.\/escape\.md/,
104+ );
105+ }),
106+ );
107+
108+ it.effect("truncates text files larger than the preview byte limit", () =>
109+ Effect.gen(function* () {
110+ const workspaceFileSystem = yield* WorkspaceFileSystem.WorkspaceFileSystem;
111+ const cwd = yield* makeTempDir;
112+ yield* writeTextFile(cwd, "large.txt", `${"a".repeat(PROJECT_READ_FILE_MAX_BYTES)}tail`);
113+
114+ const result = yield* workspaceFileSystem.readFile({
115+ cwd,
116+ relativePath: "large.txt",
117+ });
118+
119+ assert.strictEqual(result.byteLength, PROJECT_READ_FILE_MAX_BYTES + 4);
120+ assert.strictEqual(result.contents.length, PROJECT_READ_FILE_MAX_BYTES);
121+ assert.strictEqual(result.contents, "a".repeat(PROJECT_READ_FILE_MAX_BYTES));
122+ assert.strictEqual(result.truncated, true);
123+ }),
124+ );
125+
126+ it.effect("rejects binary files before decoding them as text", () =>
127+ Effect.gen(function* () {
128+ const workspaceFileSystem = yield* WorkspaceFileSystem.WorkspaceFileSystem;
129+ const cwd = yield* makeTempDir;
130+ yield* writeBinaryFile(cwd, "image.bin", new Uint8Array([0x66, 0x6f, 0x00, 0x6f]));
131+
132+ const error = yield* workspaceFileSystem
133+ .readFile({ cwd, relativePath: "image.bin" })
134+ .pipe(Effect.flip);
135+
136+ assert.strictEqual(
137+ error.message,
138+ `Workspace file operation 'workspaceFileSystem.readFile' failed for 'image.bin' in '${cwd}'.`,
87139 );
140+ assert.ok(error.cause instanceof Error);
141+ assert.strictEqual((error.cause as Error).message, "Binary files cannot be previewed as text.");
88142 }),
89143 );
90144
@@ -105,11 +159,13 @@ it.layer(TestLayer, { excludeTestServices: true })("WorkspaceFileSystemLive", (i · 14 unmodified lines
105159 .readFile({ cwd, relativePath: "linked-secret.txt" })
106160 .pipe(Effect.flip);
107161
108− expect(error.message).toBe(
162+ assert.strictEqual(
163+ error.message,
109164 `Workspace file operation 'workspaceFileSystem.readFile' failed for 'linked-secret.txt' in '${cwd}'.`,
110165 );
111− expect(error.cause).toBeInstanceOf(Error);
112− expect((error.cause as Error).message).toBe(
166+ assert.ok(error.cause instanceof Error);
167+ assert.strictEqual(
168+ (error.cause as Error).message,
113169 "Workspace file path resolves outside the project root.",
114170 );
115171 }),
@@ -132,8 +188,8 @@ it.layer(TestLayer, { excludeTestServices: true })("WorkspaceFileSystemLive", (i · 16 unmodified lines
132188 .readFileString(path.join(cwd, "plans/effect-rpc.md"))
133189 .pipe(Effect.orDie);
134190
135− expect(result).toEqual({ relativePath: "plans/effect-rpc.md" });
136− expect(saved).toBe("# Plan\n");
191+ assert.deepStrictEqual(result, { relativePath: "plans/effect-rpc.md" });
192+ assert.strictEqual(saved, "# Plan\n");
137193 }),
138194 );
139195
@@ -145,7 +201,8 @@ it.layer(TestLayer, { excludeTestServices: true })("WorkspaceFileSystemLive", (i · 5 unmodified lines
145201 yield* writeTextFile(cwd, "src/existing.ts", "export {};\n");
146202
147203 const beforeWrite = yield* workspaceEntries.list({ cwd });
148− expect(beforeWrite.entries.some((entry) => entry.path === "plans/effect-rpc.md")).toBe(
204+ assert.strictEqual(
205+ beforeWrite.entries.some((entry) => entry.path === "plans/effect-rpc.md"),
149206 false,
150207 );
151208
@@ -156,10 +213,11 @@ it.layer(TestLayer, { excludeTestServices: true })("WorkspaceFileSystemLive", (i · 4 unmodified lines
156213 });
157214
158215 const afterWrite = yield* workspaceEntries.list({ cwd });
159− expect(afterWrite.entries).toEqual(
160− expect.arrayContaining([expect.objectContaining({ path: "plans/effect-rpc.md" })]),
216+ assert.strictEqual(
217+ afterWrite.entries.some((entry) => entry.path === "plans/effect-rpc.md"),
218+ true,
161219 );
162− expect(afterWrite.truncated).toBe(false);
220+ assert.strictEqual(afterWrite.truncated, false);
163221 }),
164222 );
165223
@@ -178,15 +236,14 @@ it.layer(TestLayer, { excludeTestServices: true })("WorkspaceFileSystemLive", (i · 12 unmodified lines
178236 })
179237 .pipe(Effect.flip);
180238
181− expect(error.message).toContain(
182− "Workspace file path must be relative to the project root: ../escape.md",
239+ assert.match(
240+ error.message,
241+ /Workspace file path must be relative to the project root: \.\.\/escape\.md/,
183242 );
184243
185244 const escapedPath = path.resolve(cwd, "..", "escape.md");
186− const escapedStat = yield* fileSystem
187− .stat(escapedPath)
188− .pipe(Effect.orElseSucceed(() => null));
189− expect(escapedStat).toBeNull();
245+ const escapedExists = yield* fileSystem.exists(escapedPath);
246+ assert.strictEqual(escapedExists, false);
190247 }),
191248 );
192249 });