History

Extract project/workspace related functionality to Effect services (#1524)

Julius Marminge committed 7 months ago

Commit 2d038471 parent d303ac326 files · +2077 −1140Browse files

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

Unified · Split

apps/server/src/workspace/Layers/WorkspaceFileSystem.test.ts+135−0
@@ -0,0 +1,135 @@
1import * as NodeServices from "@effect/platform-node/NodeServices";
2import { it, describe, expect } from "@effect/vitest";
3import { Effect, FileSystem, Layer, Path } from "effect";
4
5import { ServerConfig } from "../../config.ts";
6import { GitCoreLive } from "../../git/Layers/GitCore.ts";
7import { WorkspaceEntries } from "../Services/WorkspaceEntries.ts";
8import { WorkspaceFileSystem } from "../Services/WorkspaceFileSystem.ts";
9import { WorkspaceEntriesLive } from "./WorkspaceEntries.ts";
10import { WorkspaceFileSystemLive } from "./WorkspaceFileSystem.ts";
11import { WorkspacePathsLive } from "./WorkspacePaths.ts";
12
13const ProjectLayer = WorkspaceFileSystemLive.pipe(
14 Layer.provide(WorkspacePathsLive),
15 Layer.provide(WorkspaceEntriesLive),
16);
17
18const TestLayer = Layer.empty.pipe(
19 Layer.provideMerge(ProjectLayer),
20 Layer.provideMerge(WorkspaceEntriesLive),
21 Layer.provideMerge(GitCoreLive),
22 Layer.provide(
23 ServerConfig.layerTest(process.cwd(), {
24 prefix: "t3-workspace-files-test-",
25 }),
26 ),
27 Layer.provideMerge(NodeServices.layer),
28);
29
30const makeTempDir = Effect.gen(function* () {
31 const fileSystem = yield* FileSystem.FileSystem;
32 return yield* fileSystem.makeTempDirectoryScoped({
33 prefix: "t3code-workspace-files-",
34 });
35});
36
37const writeTextFile = Effect.fn("writeTextFile")(function* (
38 cwd: string,
39 relativePath: string,
40 contents = "",
41) {
42 const fileSystem = yield* FileSystem.FileSystem;
43 const path = yield* Path.Path;
44 const absolutePath = path.join(cwd, relativePath);
45 yield* fileSystem
46 .makeDirectory(path.dirname(absolutePath), { recursive: true })
47 .pipe(Effect.orDie);
48 yield* fileSystem.writeFileString(absolutePath, contents).pipe(Effect.orDie);
49});
50
51it.layer(TestLayer)("WorkspaceFileSystemLive", (it) => {
52 describe("writeFile", () => {
53 it.effect("writes files relative to the workspace root", () =>
54 Effect.gen(function* () {
55 const workspaceFileSystem = yield* WorkspaceFileSystem;
56 const cwd = yield* makeTempDir;
57 const fileSystem = yield* FileSystem.FileSystem;
58 const path = yield* Path.Path;
59 const result = yield* workspaceFileSystem.writeFile({
60 cwd,
61 relativePath: "plans/effect-rpc.md",
62 contents: "# Plan\n",
63 });
64 const saved = yield* fileSystem
65 .readFileString(path.join(cwd, "plans/effect-rpc.md"))
66 .pipe(Effect.orDie);
67
68 expect(result).toEqual({ relativePath: "plans/effect-rpc.md" });
69 expect(saved).toBe("# Plan\n");
70 }),
71 );
72
73 it.effect("invalidates workspace entry search cache after writes", () =>
74 Effect.gen(function* () {
75 const workspaceEntries = yield* WorkspaceEntries;
76 const workspaceFileSystem = yield* WorkspaceFileSystem;
77 const cwd = yield* makeTempDir;
78 yield* writeTextFile(cwd, "src/existing.ts", "export {};\n");
79
80 const beforeWrite = yield* workspaceEntries.search({
81 cwd,
82 query: "rpc",
83 limit: 10,
84 });
85 expect(beforeWrite).toEqual({
86 entries: [],
87 truncated: false,
88 });
89
90 yield* workspaceFileSystem.writeFile({
91 cwd,
92 relativePath: "plans/effect-rpc.md",
93 contents: "# Plan\n",
94 });
95
96 const afterWrite = yield* workspaceEntries.search({
97 cwd,
98 query: "rpc",
99 limit: 10,
100 });
101 expect(afterWrite.entries).toEqual(
102 expect.arrayContaining([expect.objectContaining({ path: "plans/effect-rpc.md" })]),
103 );
104 expect(afterWrite.truncated).toBe(false);
105 }),
106 );
107
108 it.effect("rejects writes outside the workspace root", () =>
109 Effect.gen(function* () {
110 const workspaceFileSystem = yield* WorkspaceFileSystem;
111 const cwd = yield* makeTempDir;
112 const path = yield* Path.Path;
113 const fileSystem = yield* FileSystem.FileSystem;
114
115 const error = yield* workspaceFileSystem
116 .writeFile({
117 cwd,
118 relativePath: "../escape.md",
119 contents: "# nope\n",
120 })
121 .pipe(Effect.flip);
122
123 expect(error.message).toContain(
124 "Workspace file path must be relative to the project root: ../escape.md",
125 );
126
127 const escapedPath = path.resolve(cwd, "..", "escape.md");
128 const escapedStat = yield* fileSystem
129 .stat(escapedPath)
130 .pipe(Effect.catch(() => Effect.succeed(null)));
131 expect(escapedStat).toBeNull();
132 }),
133 );
134 });
135});