History

Refactor filesystem and VCS config Effect usage

Cursor Agent committed 4 months ago

Commit cb1e1691 parent 3bdaa6e4 files · +230 −83Browse files

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

Unified · Split

apps/server/src/workspace/Layers/WorkspaceFileSystem.test.ts+79−14
@@ -1,5 +1,5 @@
1import * as NodeServices from "@effect/platform-node/NodeServices";1import * as NodeServices from "@effect/platform-node/NodeServices";
2import { it, describe, expect } from "@effect/vitest";2import { assert, it, describe } from "@effect/vitest";
3import * as Effect from "effect/Effect";3import * as Effect from "effect/Effect";
4import * as FileSystem from "effect/FileSystem";4import * as FileSystem from "effect/FileSystem";
5import * as Layer from "effect/Layer";5import * as Layer from "effect/Layer";
@@ -18,6 +18,8 @@ const ProjectLayer = WorkspaceFileSystemLive.pipe( · 12 unmodified lines
18 Layer.provide(WorkspaceEntries.layer.pipe(Layer.provide(WorkspacePathsLive))),18 Layer.provide(WorkspaceEntries.layer.pipe(Layer.provide(WorkspacePathsLive))),
19);19);
20 20
21const PROJECT_READ_FILE_MAX_BYTES = 1024 * 1024;
22
21const TestLayer = Layer.empty.pipe(23const TestLayer = Layer.empty.pipe(
22 Layer.provideMerge(ProjectLayer),24 Layer.provideMerge(ProjectLayer),
23 Layer.provideMerge(WorkspaceEntries.layer.pipe(Layer.provide(WorkspacePathsLive))),25 Layer.provideMerge(WorkspaceEntries.layer.pipe(Layer.provide(WorkspacePathsLive))),
@@ -52,6 +54,20 @@ const writeTextFile = Effect.fn("writeTextFile")(function* ( · 28 unmodified lines
52 yield* fileSystem.writeFileString(absolutePath, contents).pipe(Effect.orDie);54 yield* fileSystem.writeFileString(absolutePath, contents).pipe(Effect.orDie);
53});55});
54 56
57const writeBinaryFile = Effect.fn("writeBinaryFile")(function* (
58 cwd: string,
59 relativePath: string,
60 contents: Uint8Array,
61) {
62 const fileSystem = yield* FileSystem.FileSystem;
63 const path = yield* Path.Path;
64 const absolutePath = path.join(cwd, relativePath);
65 yield* fileSystem
66 .makeDirectory(path.dirname(absolutePath), { recursive: true })
67 .pipe(Effect.orDie);
68 yield* fileSystem.writeFile(absolutePath, contents).pipe(Effect.orDie);
69});
70
55it.layer(TestLayer, { excludeTestServices: true })("WorkspaceFileSystemLive", (it) => {71it.layer(TestLayer, { excludeTestServices: true })("WorkspaceFileSystemLive", (it) => {
56 describe("readFile", () => {72 describe("readFile", () => {
57 it.effect("reads UTF-8 files relative to the workspace root", () =>73 it.effect("reads UTF-8 files relative to the workspace root", () =>
@@ -65,7 +81,7 @@ it.layer(TestLayer, { excludeTestServices: true })("WorkspaceFileSystemLive", (i · 7 unmodified lines
65 relativePath: "src/index.ts",81 relativePath: "src/index.ts",
66 });82 });
67 83
68 expect(result).toEqual({84 assert.deepStrictEqual(result, {
69 relativePath: "src/index.ts",85 relativePath: "src/index.ts",
70 contents: "export const answer = 42;\n",86 contents: "export const answer = 42;\n",
71 byteLength: 26,87 byteLength: 26,
@@ -83,7 +99,8 @@ it.layer(TestLayer, { excludeTestServices: true })("WorkspaceFileSystemLive", (i · 11 unmodified lines
83 .readFile({ cwd, relativePath: "../escape.md" })99 .readFile({ cwd, relativePath: "../escape.md" })
84 .pipe(Effect.flip);100 .pipe(Effect.flip);
85 101
86 expect(error.message).toContain(102 assert.include(
103 error.message,
87 "Workspace file path must be relative to the project root: ../escape.md",104 "Workspace file path must be relative to the project root: ../escape.md",
88 );105 );
89 }),106 }),
@@ -106,7 +123,56 @@ it.layer(TestLayer, { excludeTestServices: true })("WorkspaceFileSystemLive", (i · 16 unmodified lines
106 .readFile({ cwd, relativePath: "linked-secret.txt" })123 .readFile({ cwd, relativePath: "linked-secret.txt" })
107 .pipe(Effect.flip);124 .pipe(Effect.flip);
108 125
109 expect(error.message).toContain("resolves outside the project root");126 assert.include(error.message, "resolves outside the project root");
127 }),
128 );
129
130 it.effect("truncates files larger than the preview byte limit", () =>
131 Effect.gen(function* () {
132 const workspaceFileSystem = yield* WorkspaceFileSystem;
133 const cwd = yield* makeTempDir;
134 const contents = `${"a".repeat(PROJECT_READ_FILE_MAX_BYTES)}tail`;
135 yield* writeTextFile(cwd, "large.txt", contents);
136
137 const result = yield* workspaceFileSystem.readFile({
138 cwd,
139 relativePath: "large.txt",
140 });
141
142 assert.equal(result.contents.length, PROJECT_READ_FILE_MAX_BYTES);
143 assert.equal(result.contents, "a".repeat(PROJECT_READ_FILE_MAX_BYTES));
144 assert.equal(result.byteLength, PROJECT_READ_FILE_MAX_BYTES + 4);
145 assert.isTrue(result.truncated);
146 }),
147 );
148
149 it.effect("rejects binary files", () =>
150 Effect.gen(function* () {
151 const workspaceFileSystem = yield* WorkspaceFileSystem;
152 const cwd = yield* makeTempDir;
153 yield* writeBinaryFile(cwd, "binary.bin", new Uint8Array([65, 0, 66]));
154
155 const error = yield* workspaceFileSystem
156 .readFile({ cwd, relativePath: "binary.bin" })
157 .pipe(Effect.flip);
158
159 assert.include(error.message, "Binary files cannot be previewed as text.");
160 }),
161 );
162
163 it.effect("rejects directories", () =>
164 Effect.gen(function* () {
165 const workspaceFileSystem = yield* WorkspaceFileSystem;
166 const fileSystem = yield* FileSystem.FileSystem;
167 const path = yield* Path.Path;
168 const cwd = yield* makeTempDir;
169 yield* fileSystem.makeDirectory(path.join(cwd, "src"), { recursive: true });
170
171 const error = yield* workspaceFileSystem
172 .readFile({ cwd, relativePath: "src" })
173 .pipe(Effect.flip);
174
175 assert.include(error.message, "Workspace path is not a file.");
110 }),176 }),
111 );177 );
112 });178 });
@@ -127,8 +193,8 @@ it.layer(TestLayer, { excludeTestServices: true })("WorkspaceFileSystemLive", (i · 14 unmodified lines
127 .readFileString(path.join(cwd, "plans/effect-rpc.md"))193 .readFileString(path.join(cwd, "plans/effect-rpc.md"))
128 .pipe(Effect.orDie);194 .pipe(Effect.orDie);
129 195
130 expect(result).toEqual({ relativePath: "plans/effect-rpc.md" });196 assert.deepStrictEqual(result, { relativePath: "plans/effect-rpc.md" });
131 expect(saved).toBe("# Plan\n");197 assert.equal(saved, "# Plan\n");
132 }),198 }),
133 );199 );
134 200
@@ -140,9 +206,7 @@ it.layer(TestLayer, { excludeTestServices: true })("WorkspaceFileSystemLive", (i · 5 unmodified lines
140 yield* writeTextFile(cwd, "src/existing.ts", "export {};\n");206 yield* writeTextFile(cwd, "src/existing.ts", "export {};\n");
141 207
142 const beforeWrite = yield* workspaceEntries.list({ cwd });208 const beforeWrite = yield* workspaceEntries.list({ cwd });
143 expect(beforeWrite.entries.some((entry) => entry.path === "plans/effect-rpc.md")).toBe(209 assert.isFalse(beforeWrite.entries.some((entry) => entry.path === "plans/effect-rpc.md"));
144 false,
145 );
146 210
147 yield* workspaceFileSystem.writeFile({211 yield* workspaceFileSystem.writeFile({
148 cwd,212 cwd,
@@ -151,10 +215,10 @@ it.layer(TestLayer, { excludeTestServices: true })("WorkspaceFileSystemLive", (i · 2 unmodified lines
151 });215 });
152 216
153 const afterWrite = yield* workspaceEntries.list({ cwd });217 const afterWrite = yield* workspaceEntries.list({ cwd });
154 expect(afterWrite.entries).toEqual(218 assert.isTrue(
155 expect.arrayContaining([expect.objectContaining({ path: "plans/effect-rpc.md" })]),219 afterWrite.entries.some((entry) => entry.path === "plans/effect-rpc.md"),
156 );220 );
157 expect(afterWrite.truncated).toBe(false);221 assert.isFalse(afterWrite.truncated);
158 }),222 }),
159 );223 );
160 224
@@ -173,7 +237,8 @@ it.layer(TestLayer, { excludeTestServices: true })("WorkspaceFileSystemLive", (i · 12 unmodified lines
173 })237 })
174 .pipe(Effect.flip);238 .pipe(Effect.flip);
175 239
176 expect(error.message).toContain(240 assert.include(
241 error.message,
177 "Workspace file path must be relative to the project root: ../escape.md",242 "Workspace file path must be relative to the project root: ../escape.md",
178 );243 );
179 244
@@ -181,7 +246,7 @@ it.layer(TestLayer, { excludeTestServices: true })("WorkspaceFileSystemLive", (i · 1 unmodified line
181 const escapedStat = yield* fileSystem246 const escapedStat = yield* fileSystem
182 .stat(escapedPath)247 .stat(escapedPath)
183 .pipe(Effect.orElseSucceed(() => null));248 .pipe(Effect.orElseSucceed(() => null));
184 expect(escapedStat).toBeNull();249 assert.isNull(escapedStat);
185 }),250 }),
186 );251 );
187 });252 });