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 @@
1+import * as NodeServices from "@effect/platform-node/NodeServices";
2+import { it, describe, expect } from "@effect/vitest";
3+import { Effect, FileSystem, Layer, Path } from "effect";
4+
5+import { ServerConfig } from "../../config.ts";
6+import { GitCoreLive } from "../../git/Layers/GitCore.ts";
7+import { WorkspaceEntries } from "../Services/WorkspaceEntries.ts";
8+import { WorkspaceFileSystem } from "../Services/WorkspaceFileSystem.ts";
9+import { WorkspaceEntriesLive } from "./WorkspaceEntries.ts";
10+import { WorkspaceFileSystemLive } from "./WorkspaceFileSystem.ts";
11+import { WorkspacePathsLive } from "./WorkspacePaths.ts";
12+
13+const ProjectLayer = WorkspaceFileSystemLive.pipe(
14+ Layer.provide(WorkspacePathsLive),
15+ Layer.provide(WorkspaceEntriesLive),
16+);
17+
18+const 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+
30+const makeTempDir = Effect.gen(function* () {
31+ const fileSystem = yield* FileSystem.FileSystem;
32+ return yield* fileSystem.makeTempDirectoryScoped({
33+ prefix: "t3code-workspace-files-",
34+ });
35+});
36+
37+const 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+
51+it.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+});