Showing the changes to apps/server/src/workspace/WorkspaceFileSystem.test.ts (1 of 2 files). Show all
Unified · Split
| @@ -1,9 +1,11 @@ | |||
| 1 | import * as NodeServices from "@effect/platform-node/NodeServices"; | 1 | import * as NodeServices from "@effect/platform-node/NodeServices"; |
| 2 | import { it, describe, expect } from "@effect/vitest"; | 2 | import { assert, describe, it } from "@effect/vitest"; |
| 3 | import * as Effect from "effect/Effect"; | 3 | import * as Effect from "effect/Effect"; |
| 4 | import * as FileSystem from "effect/FileSystem"; | 4 | import * as FileSystem from "effect/FileSystem"; |
| 5 | import * as Layer from "effect/Layer"; | 5 | import * as Layer from "effect/Layer"; |
| 6 | import * as Option from "effect/Option"; | ||
| 6 | import * as Path from "effect/Path"; | 7 | import * as Path from "effect/Path"; |
| 8 | import * as PlatformError from "effect/PlatformError"; | ||
| 7 | 9 | ||
| 8 | import * as ServerConfig from "../config.ts"; | 10 | import * as ServerConfig from "../config.ts"; |
| 9 | import * as VcsDriverRegistry from "../vcs/VcsDriverRegistry.ts"; | 11 | import * as VcsDriverRegistry from "../vcs/VcsDriverRegistry.ts"; |
| @@ -64,7 +66,7 @@ it.layer(TestLayer, { excludeTestServices: true })("WorkspaceFileSystemLive", (i · 54 unmodified lines | |||
| 64 | relativePath: "src/index.ts", | 66 | relativePath: "src/index.ts", |
| 65 | }); | 67 | }); |
| 66 | 68 | ||
| 67 | expect(result).toEqual({ | 69 | assert.deepStrictEqual(result, { |
| 68 | relativePath: "src/index.ts", | 70 | relativePath: "src/index.ts", |
| 69 | contents: "export const answer = 42;\n", | 71 | contents: "export const answer = 42;\n", |
| 70 | byteLength: 26, | 72 | byteLength: 26, |
| @@ -82,7 +84,8 @@ it.layer(TestLayer, { excludeTestServices: true })("WorkspaceFileSystemLive", (i · 11 unmodified lines | |||
| 82 | .readFile({ cwd, relativePath: "../escape.md" }) | 84 | .readFile({ cwd, relativePath: "../escape.md" }) |
| 83 | .pipe(Effect.flip); | 85 | .pipe(Effect.flip); |
| 84 | 86 | ||
| 85 | expect(error.message).toContain( | 87 | assert.include( |
| 88 | error.message, | ||
| 86 | "Workspace file path must be relative to the project root: ../escape.md", | 89 | "Workspace file path must be relative to the project root: ../escape.md", |
| 87 | ); | 90 | ); |
| 88 | }), | 91 | }), |
| @@ -107,14 +110,14 @@ it.layer(TestLayer, { excludeTestServices: true })("WorkspaceFileSystemLive", (i · 18 unmodified lines | |||
| 107 | const resolvedWorkspaceRoot = yield* fileSystem.realPath(cwd); | 110 | const resolvedWorkspaceRoot = yield* fileSystem.realPath(cwd); |
| 108 | const resolvedPath = yield* fileSystem.realPath(path.join(outsideDir, "secret.txt")); | 111 | const resolvedPath = yield* fileSystem.realPath(path.join(outsideDir, "secret.txt")); |
| 109 | 112 | ||
| 110 | expect(error).toBeInstanceOf(WorkspaceFileSystem.WorkspaceFilePathEscapeError); | 113 | assert.strictEqual(error._tag, "WorkspaceFilePathEscapeError"); |
| 111 | expect(error).toMatchObject({ | 114 | assert.deepInclude(error, { |
| 112 | workspaceRoot: cwd, | 115 | workspaceRoot: cwd, |
| 113 | relativePath: "linked-secret.txt", | 116 | relativePath: "linked-secret.txt", |
| 114 | resolvedWorkspaceRoot, | 117 | resolvedWorkspaceRoot, |
| 115 | resolvedPath, | 118 | resolvedPath, |
| 116 | }); | 119 | }); |
| 117 | expect("cause" in error).toBe(false); | 120 | assert.notProperty(error, "cause"); |
| 118 | }), | 121 | }), |
| 119 | ); | 122 | ); |
| 120 | 123 | ||
| @@ -131,13 +134,13 @@ it.layer(TestLayer, { excludeTestServices: true })("WorkspaceFileSystemLive", (i · 10 unmodified lines | |||
| 131 | .pipe(Effect.flip); | 134 | .pipe(Effect.flip); |
| 132 | const resolvedPath = yield* fileSystem.realPath(path.join(cwd, "src")); | 135 | const resolvedPath = yield* fileSystem.realPath(path.join(cwd, "src")); |
| 133 | 136 | ||
| 134 | expect(error).toBeInstanceOf(WorkspaceFileSystem.WorkspacePathNotFileError); | 137 | assert.strictEqual(error._tag, "WorkspacePathNotFileError"); |
| 135 | expect(error).toMatchObject({ | 138 | assert.deepInclude(error, { |
| 136 | workspaceRoot: cwd, | 139 | workspaceRoot: cwd, |
| 137 | relativePath: "src", | 140 | relativePath: "src", |
| 138 | resolvedPath, | 141 | resolvedPath, |
| 139 | }); | 142 | }); |
| 140 | expect("cause" in error).toBe(false); | 143 | assert.notProperty(error, "cause"); |
| 141 | }), | 144 | }), |
| 142 | ); | 145 | ); |
| 143 | 146 | ||
| @@ -155,14 +158,31 @@ it.layer(TestLayer, { excludeTestServices: true })("WorkspaceFileSystemLive", (i · 11 unmodified lines | |||
| 155 | .pipe(Effect.flip); | 158 | .pipe(Effect.flip); |
| 156 | const resolvedPath = yield* fileSystem.realPath(absolutePath); | 159 | const resolvedPath = yield* fileSystem.realPath(absolutePath); |
| 157 | 160 | ||
| 158 | expect(error).toBeInstanceOf(WorkspaceFileSystem.WorkspaceBinaryFileError); | 161 | assert.strictEqual(error._tag, "WorkspaceBinaryFileError"); |
| 159 | expect(error).toMatchObject({ | 162 | assert.deepInclude(error, { |
| 160 | workspaceRoot: cwd, | 163 | workspaceRoot: cwd, |
| 161 | relativePath: "asset.bin", | 164 | relativePath: "asset.bin", |
| 162 | resolvedPath, | 165 | resolvedPath, |
| 163 | }); | 166 | }); |
| 164 | expect("cause" in error).toBe(false); | 167 | assert.notProperty(error, "cause"); |
| 165 | expect("contents" in error).toBe(false); | 168 | assert.notProperty(error, "contents"); |
| 169 | }), | ||
| 170 | ); | ||
| 171 | |||
| 172 | it.effect("reads at most the preview limit", () => | ||
| 173 | Effect.gen(function* () { | ||
| 174 | const workspaceFileSystem = yield* WorkspaceFileSystem.WorkspaceFileSystem; | ||
| 175 | const cwd = yield* makeTempDir; | ||
| 176 | yield* writeTextFile(cwd, "large.txt", "a".repeat(1024 * 1024 + 1)); | ||
| 177 | |||
| 178 | const result = yield* workspaceFileSystem.readFile({ | ||
| 179 | cwd, | ||
| 180 | relativePath: "large.txt", | ||
| 181 | }); | ||
| 182 | |||
| 183 | assert.strictEqual(result.contents.length, 1024 * 1024); | ||
| 184 | assert.strictEqual(result.byteLength, 1024 * 1024 + 1); | ||
| 185 | assert.isTrue(result.truncated); | ||
| 166 | }), | 186 | }), |
| 167 | ); | 187 | ); |
| 168 | 188 | ||
| @@ -177,16 +197,19 @@ it.layer(TestLayer, { excludeTestServices: true })("WorkspaceFileSystemLive", (i · 8 unmodified lines | |||
| 177 | .readFile({ cwd, relativePath: "missing.txt" }) | 197 | .readFile({ cwd, relativePath: "missing.txt" }) |
| 178 | .pipe(Effect.flip); | 198 | .pipe(Effect.flip); |
| 179 | 199 | ||
| 180 | expect(error).toBeInstanceOf(WorkspaceFileSystem.WorkspaceFileSystemOperationError); | 200 | assert.strictEqual(error._tag, "WorkspaceFileSystemOperationError"); |
| 181 | expect(error).toMatchObject({ | 201 | assert.deepInclude(error, { |
| 182 | workspaceRoot: cwd, | 202 | workspaceRoot: cwd, |
| 183 | relativePath: "missing.txt", | 203 | relativePath: "missing.txt", |
| 184 | resolvedPath, | 204 | resolvedPath, |
| 185 | operationPath: resolvedPath, | 205 | operationPath: resolvedPath, |
| 186 | operation: "realpath-target", | 206 | operation: "realpath-target", |
| 187 | }); | 207 | }); |
| 188 | expect(error.cause).toBeInstanceOf(Error); | 208 | assert.instanceOf(error.cause, PlatformError.PlatformError); |
| 189 | expect((error.cause as NodeJS.ErrnoException).code).toBe("ENOENT"); | 209 | assert.strictEqual( |
| 210 | (error.cause as PlatformError.PlatformError).reason._tag, | ||
| 211 | "NotFound", | ||
| 212 | ); | ||
| 190 | }), | 213 | }), |
| 191 | ); | 214 | ); |
| 192 | }); | 215 | }); |
| @@ -207,8 +230,8 @@ it.layer(TestLayer, { excludeTestServices: true })("WorkspaceFileSystemLive", (i · 14 unmodified lines | |||
| 207 | .readFileString(path.join(cwd, "plans/effect-rpc.md")) | 230 | .readFileString(path.join(cwd, "plans/effect-rpc.md")) |
| 208 | .pipe(Effect.orDie); | 231 | .pipe(Effect.orDie); |
| 209 | 232 | ||
| 210 | expect(result).toEqual({ relativePath: "plans/effect-rpc.md" }); | 233 | assert.deepStrictEqual(result, { relativePath: "plans/effect-rpc.md" }); |
| 211 | expect(saved).toBe("# Plan\n"); | 234 | assert.strictEqual(saved, "# Plan\n"); |
| 212 | }), | 235 | }), |
| 213 | ); | 236 | ); |
| 214 | 237 | ||
| @@ -220,8 +243,8 @@ it.layer(TestLayer, { excludeTestServices: true })("WorkspaceFileSystemLive", (i · 5 unmodified lines | |||
| 220 | yield* writeTextFile(cwd, "src/existing.ts", "export {};\n"); | 243 | yield* writeTextFile(cwd, "src/existing.ts", "export {};\n"); |
| 221 | 244 | ||
| 222 | const beforeWrite = yield* workspaceEntries.list({ cwd }); | 245 | const beforeWrite = yield* workspaceEntries.list({ cwd }); |
| 223 | expect(beforeWrite.entries.some((entry) => entry.path === "plans/effect-rpc.md")).toBe( | 246 | assert.isFalse( |
| 224 | false, | 247 | beforeWrite.entries.some((entry) => entry.path === "plans/effect-rpc.md"), |
| 225 | ); | 248 | ); |
| 226 | 249 | ||
| 227 | yield* workspaceFileSystem.writeFile({ | 250 | yield* workspaceFileSystem.writeFile({ |
| @@ -231,10 +254,10 @@ it.layer(TestLayer, { excludeTestServices: true })("WorkspaceFileSystemLive", (i · 3 unmodified lines | |||
| 231 | }); | 254 | }); |
| 232 | 255 | ||
| 233 | const afterWrite = yield* workspaceEntries.list({ cwd }); | 256 | const afterWrite = yield* workspaceEntries.list({ cwd }); |
| 234 | expect(afterWrite.entries).toEqual( | 257 | assert.isTrue( |
| 235 | expect.arrayContaining([expect.objectContaining({ path: "plans/effect-rpc.md" })]), | 258 | afterWrite.entries.some((entry) => entry.path === "plans/effect-rpc.md"), |
| 236 | ); | 259 | ); |
| 237 | expect(afterWrite.truncated).toBe(false); | 260 | assert.isFalse(afterWrite.truncated); |
| 238 | }), | 261 | }), |
| 239 | ); | 262 | ); |
| 240 | 263 | ||
| @@ -253,15 +276,14 @@ it.layer(TestLayer, { excludeTestServices: true })("WorkspaceFileSystemLive", (i · 12 unmodified lines | |||
| 253 | }) | 276 | }) |
| 254 | .pipe(Effect.flip); | 277 | .pipe(Effect.flip); |
| 255 | 278 | ||
| 256 | expect(error.message).toContain( | 279 | assert.include( |
| 280 | error.message, | ||
| 257 | "Workspace file path must be relative to the project root: ../escape.md", | 281 | "Workspace file path must be relative to the project root: ../escape.md", |
| 258 | ); | 282 | ); |
| 259 | 283 | ||
| 260 | const escapedPath = path.resolve(cwd, "..", "escape.md"); | 284 | const escapedPath = path.resolve(cwd, "..", "escape.md"); |
| 261 | const escapedStat = yield* fileSystem | 285 | const escapedStat = yield* fileSystem.stat(escapedPath).pipe(Effect.option); |
| 262 | .stat(escapedPath) | 286 | assert.isTrue(Option.isNone(escapedStat)); |
| 263 | .pipe(Effect.orElseSucceed(() => null)); | ||
| 264 | expect(escapedStat).toBeNull(); | ||
| 265 | }), | 287 | }), |
| 266 | ); | 288 | ); |
| 267 | }); | 289 | }); |