History

[codex] Refactor project and workspace Effect services (#3190)

Julius Marminge committed 4 months ago

Commit b0a3a501 parent 79c571755 files · +1211 −1220Browse files

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

Unified · Split

apps/server/src/workspace/WorkspaceFileSystem.test.ts+193−0
@@ -0,0 +1,193 @@
1import * as NodeServices from "@effect/platform-node/NodeServices";
2import { it, describe, expect } from "@effect/vitest";
3import * as Effect from "effect/Effect";
4import * as FileSystem from "effect/FileSystem";
5import * as Layer from "effect/Layer";
6import * as Path from "effect/Path";
7
8import * as ServerConfig from "../config.ts";
9import * as VcsDriverRegistry from "../vcs/VcsDriverRegistry.ts";
10import * as VcsProcess from "../vcs/VcsProcess.ts";
11import * as WorkspaceEntries from "./WorkspaceEntries.ts";
12import * as WorkspaceFileSystem from "./WorkspaceFileSystem.ts";
13import * as WorkspacePaths from "./WorkspacePaths.ts";
14
15const ProjectLayer = WorkspaceFileSystem.layer.pipe(
16 Layer.provide(WorkspacePaths.layer),
17 Layer.provide(WorkspaceEntries.layer.pipe(Layer.provide(WorkspacePaths.layer))),
18);
19
20const TestLayer = Layer.empty.pipe(
21 Layer.provideMerge(ProjectLayer),
22 Layer.provideMerge(WorkspaceEntries.layer.pipe(Layer.provide(WorkspacePaths.layer))),
23 Layer.provideMerge(WorkspacePaths.layer),
24 Layer.provideMerge(VcsDriverRegistry.layer.pipe(Layer.provide(VcsProcess.layer))),
25 Layer.provide(
26 ServerConfig.ServerConfig.layerTest(process.cwd(), {
27 prefix: "t3-workspace-files-test-",
28 }),
29 ),
30 Layer.provideMerge(NodeServices.layer),
31);
32
33const makeTempDir = Effect.gen(function* () {
34 const fileSystem = yield* FileSystem.FileSystem;
35 return yield* fileSystem.makeTempDirectoryScoped({
36 prefix: "t3code-workspace-files-",
37 });
38});
39
40const writeTextFile = Effect.fn("writeTextFile")(function* (
41 cwd: string,
42 relativePath: string,
43 contents = "",
44) {
45 const fileSystem = yield* FileSystem.FileSystem;
46 const path = yield* Path.Path;
47 const absolutePath = path.join(cwd, relativePath);
48 yield* fileSystem
49 .makeDirectory(path.dirname(absolutePath), { recursive: true })
50 .pipe(Effect.orDie);
51 yield* fileSystem.writeFileString(absolutePath, contents).pipe(Effect.orDie);
52});
53
54it.layer(TestLayer, { excludeTestServices: true })("WorkspaceFileSystemLive", (it) => {
55 describe("readFile", () => {
56 it.effect("reads UTF-8 files relative to the workspace root", () =>
57 Effect.gen(function* () {
58 const workspaceFileSystem = yield* WorkspaceFileSystem.WorkspaceFileSystem;
59 const cwd = yield* makeTempDir;
60 yield* writeTextFile(cwd, "src/index.ts", "export const answer = 42;\n");
61
62 const result = yield* workspaceFileSystem.readFile({
63 cwd,
64 relativePath: "src/index.ts",
65 });
66
67 expect(result).toEqual({
68 relativePath: "src/index.ts",
69 contents: "export const answer = 42;\n",
70 byteLength: 26,
71 truncated: false,
72 });
73 }),
74 );
75
76 it.effect("rejects reads outside the workspace root", () =>
77 Effect.gen(function* () {
78 const workspaceFileSystem = yield* WorkspaceFileSystem.WorkspaceFileSystem;
79 const cwd = yield* makeTempDir;
80
81 const error = yield* workspaceFileSystem
82 .readFile({ cwd, relativePath: "../escape.md" })
83 .pipe(Effect.flip);
84
85 expect(error.message).toContain(
86 "Workspace file path must be relative to the project root: ../escape.md",
87 );
88 }),
89 );
90
91 it.effect("rejects symlinks that resolve outside the workspace root", () =>
92 Effect.gen(function* () {
93 const workspaceFileSystem = yield* WorkspaceFileSystem.WorkspaceFileSystem;
94 const fileSystem = yield* FileSystem.FileSystem;
95 const path = yield* Path.Path;
96 const cwd = yield* makeTempDir;
97 const outsideDir = yield* makeTempDir;
98 yield* writeTextFile(outsideDir, "secret.txt", "outside\n");
99 yield* fileSystem.symlink(
100 path.join(outsideDir, "secret.txt"),
101 path.join(cwd, "linked-secret.txt"),
102 );
103
104 const error = yield* workspaceFileSystem
105 .readFile({ cwd, relativePath: "linked-secret.txt" })
106 .pipe(Effect.flip);
107
108 expect(error.message).toBe(
109 `Workspace file operation 'workspaceFileSystem.readFile' failed for 'linked-secret.txt' in '${cwd}'.`,
110 );
111 expect(error.cause).toBeInstanceOf(Error);
112 expect((error.cause as Error).message).toBe(
113 "Workspace file path resolves outside the project root.",
114 );
115 }),
116 );
117 });
118
119 describe("writeFile", () => {
120 it.effect("writes files relative to the workspace root", () =>
121 Effect.gen(function* () {
122 const workspaceFileSystem = yield* WorkspaceFileSystem.WorkspaceFileSystem;
123 const cwd = yield* makeTempDir;
124 const fileSystem = yield* FileSystem.FileSystem;
125 const path = yield* Path.Path;
126 const result = yield* workspaceFileSystem.writeFile({
127 cwd,
128 relativePath: "plans/effect-rpc.md",
129 contents: "# Plan\n",
130 });
131 const saved = yield* fileSystem
132 .readFileString(path.join(cwd, "plans/effect-rpc.md"))
133 .pipe(Effect.orDie);
134
135 expect(result).toEqual({ relativePath: "plans/effect-rpc.md" });
136 expect(saved).toBe("# Plan\n");
137 }),
138 );
139
140 it.effect("invalidates workspace entry search cache after writes", () =>
141 Effect.gen(function* () {
142 const workspaceEntries = yield* WorkspaceEntries.WorkspaceEntries;
143 const workspaceFileSystem = yield* WorkspaceFileSystem.WorkspaceFileSystem;
144 const cwd = yield* makeTempDir;
145 yield* writeTextFile(cwd, "src/existing.ts", "export {};\n");
146
147 const beforeWrite = yield* workspaceEntries.list({ cwd });
148 expect(beforeWrite.entries.some((entry) => entry.path === "plans/effect-rpc.md")).toBe(
149 false,
150 );
151
152 yield* workspaceFileSystem.writeFile({
153 cwd,
154 relativePath: "plans/effect-rpc.md",
155 contents: "# Plan\n",
156 });
157
158 const afterWrite = yield* workspaceEntries.list({ cwd });
159 expect(afterWrite.entries).toEqual(
160 expect.arrayContaining([expect.objectContaining({ path: "plans/effect-rpc.md" })]),
161 );
162 expect(afterWrite.truncated).toBe(false);
163 }),
164 );
165
166 it.effect("rejects writes outside the workspace root", () =>
167 Effect.gen(function* () {
168 const workspaceFileSystem = yield* WorkspaceFileSystem.WorkspaceFileSystem;
169 const cwd = yield* makeTempDir;
170 const path = yield* Path.Path;
171 const fileSystem = yield* FileSystem.FileSystem;
172
173 const error = yield* workspaceFileSystem
174 .writeFile({
175 cwd,
176 relativePath: "../escape.md",
177 contents: "# nope\n",
178 })
179 .pipe(Effect.flip);
180
181 expect(error.message).toContain(
182 "Workspace file path must be relative to the project root: ../escape.md",
183 );
184
185 const escapedPath = path.resolve(cwd, "..", "escape.md");
186 const escapedStat = yield* fileSystem
187 .stat(escapedPath)
188 .pipe(Effect.orElseSucceed(() => null));
189 expect(escapedStat).toBeNull();
190 }),
191 );
192 });
193});