@cloudflare/sandbox 0.4.12 → 0.4.14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (79) hide show
  1. package/.turbo/turbo-build.log +13 -47
  2. package/CHANGELOG.md +38 -16
  3. package/Dockerfile +15 -9
  4. package/README.md +0 -1
  5. package/dist/index.d.ts +1889 -9
  6. package/dist/index.d.ts.map +1 -0
  7. package/dist/index.js +3144 -65
  8. package/dist/index.js.map +1 -1
  9. package/package.json +5 -5
  10. package/src/clients/base-client.ts +39 -24
  11. package/src/clients/command-client.ts +8 -8
  12. package/src/clients/file-client.ts +31 -26
  13. package/src/clients/git-client.ts +3 -4
  14. package/src/clients/index.ts +12 -16
  15. package/src/clients/interpreter-client.ts +51 -47
  16. package/src/clients/port-client.ts +10 -10
  17. package/src/clients/process-client.ts +11 -8
  18. package/src/clients/sandbox-client.ts +2 -4
  19. package/src/clients/types.ts +6 -2
  20. package/src/clients/utility-client.ts +10 -6
  21. package/src/errors/adapter.ts +90 -32
  22. package/src/errors/classes.ts +189 -64
  23. package/src/errors/index.ts +9 -5
  24. package/src/file-stream.ts +11 -6
  25. package/src/index.ts +22 -15
  26. package/src/interpreter.ts +50 -41
  27. package/src/request-handler.ts +24 -21
  28. package/src/sandbox.ts +339 -149
  29. package/src/security.ts +21 -6
  30. package/src/sse-parser.ts +4 -3
  31. package/src/version.ts +1 -1
  32. package/tests/base-client.test.ts +116 -80
  33. package/tests/command-client.test.ts +149 -112
  34. package/tests/file-client.test.ts +309 -197
  35. package/tests/file-stream.test.ts +24 -20
  36. package/tests/get-sandbox.test.ts +10 -10
  37. package/tests/git-client.test.ts +188 -101
  38. package/tests/port-client.test.ts +100 -108
  39. package/tests/process-client.test.ts +204 -179
  40. package/tests/request-handler.test.ts +117 -65
  41. package/tests/sandbox.test.ts +219 -67
  42. package/tests/sse-parser.test.ts +17 -16
  43. package/tests/utility-client.test.ts +79 -72
  44. package/tsdown.config.ts +12 -0
  45. package/vitest.config.ts +6 -6
  46. package/dist/chunk-BFVUNTP4.js +0 -104
  47. package/dist/chunk-BFVUNTP4.js.map +0 -1
  48. package/dist/chunk-EKSWCBCA.js +0 -86
  49. package/dist/chunk-EKSWCBCA.js.map +0 -1
  50. package/dist/chunk-JXZMAU2C.js +0 -559
  51. package/dist/chunk-JXZMAU2C.js.map +0 -1
  52. package/dist/chunk-UJ3TV4M6.js +0 -7
  53. package/dist/chunk-UJ3TV4M6.js.map +0 -1
  54. package/dist/chunk-YE265ASX.js +0 -2484
  55. package/dist/chunk-YE265ASX.js.map +0 -1
  56. package/dist/chunk-Z532A7QC.js +0 -78
  57. package/dist/chunk-Z532A7QC.js.map +0 -1
  58. package/dist/file-stream.d.ts +0 -43
  59. package/dist/file-stream.js +0 -9
  60. package/dist/file-stream.js.map +0 -1
  61. package/dist/interpreter.d.ts +0 -33
  62. package/dist/interpreter.js +0 -8
  63. package/dist/interpreter.js.map +0 -1
  64. package/dist/request-handler.d.ts +0 -18
  65. package/dist/request-handler.js +0 -13
  66. package/dist/request-handler.js.map +0 -1
  67. package/dist/sandbox-CLZWpfGc.d.ts +0 -613
  68. package/dist/sandbox.d.ts +0 -4
  69. package/dist/sandbox.js +0 -13
  70. package/dist/sandbox.js.map +0 -1
  71. package/dist/security.d.ts +0 -31
  72. package/dist/security.js +0 -13
  73. package/dist/security.js.map +0 -1
  74. package/dist/sse-parser.d.ts +0 -28
  75. package/dist/sse-parser.js +0 -11
  76. package/dist/sse-parser.js.map +0 -1
  77. package/dist/version.d.ts +0 -8
  78. package/dist/version.js +0 -7
  79. package/dist/version.js.map +0 -1
@@ -11,11 +11,11 @@ import type {
11
11
  import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
12
12
  import { FileClient } from '../src/clients/file-client';
13
13
  import {
14
- FileExistsError,
15
- FileNotFoundError,
14
+ FileExistsError,
15
+ FileNotFoundError,
16
16
  FileSystemError,
17
- PermissionDeniedError,
18
- SandboxError
17
+ PermissionDeniedError,
18
+ SandboxError
19
19
  } from '../src/errors';
20
20
 
21
21
  describe('FileClient', () => {
@@ -24,13 +24,13 @@ describe('FileClient', () => {
24
24
 
25
25
  beforeEach(() => {
26
26
  vi.clearAllMocks();
27
-
27
+
28
28
  mockFetch = vi.fn();
29
29
  global.fetch = mockFetch as unknown as typeof fetch;
30
-
30
+
31
31
  client = new FileClient({
32
32
  baseUrl: 'http://test.com',
33
- port: 3000,
33
+ port: 3000
34
34
  });
35
35
  });
36
36
 
@@ -45,13 +45,12 @@ describe('FileClient', () => {
45
45
  exitCode: 0,
46
46
  path: '/app/new-directory',
47
47
  recursive: false,
48
- timestamp: '2023-01-01T00:00:00Z',
48
+ timestamp: '2023-01-01T00:00:00Z'
49
49
  };
50
50
 
51
- mockFetch.mockResolvedValue(new Response(
52
- JSON.stringify(mockResponse),
53
- { status: 200 }
54
- ));
51
+ mockFetch.mockResolvedValue(
52
+ new Response(JSON.stringify(mockResponse), { status: 200 })
53
+ );
55
54
 
56
55
  const result = await client.mkdir('/app/new-directory', 'session-mkdir');
57
56
 
@@ -67,15 +66,18 @@ describe('FileClient', () => {
67
66
  exitCode: 0,
68
67
  path: '/app/deep/nested/directory',
69
68
  recursive: true,
70
- timestamp: '2023-01-01T00:00:00Z',
69
+ timestamp: '2023-01-01T00:00:00Z'
71
70
  };
72
71
 
73
- mockFetch.mockResolvedValue(new Response(
74
- JSON.stringify(mockResponse),
75
- { status: 200 }
76
- ));
72
+ mockFetch.mockResolvedValue(
73
+ new Response(JSON.stringify(mockResponse), { status: 200 })
74
+ );
77
75
 
78
- const result = await client.mkdir('/app/deep/nested/directory', 'session-mkdir', { recursive: true });
76
+ const result = await client.mkdir(
77
+ '/app/deep/nested/directory',
78
+ 'session-mkdir',
79
+ { recursive: true }
80
+ );
79
81
 
80
82
  expect(result.success).toBe(true);
81
83
  expect(result.recursive).toBe(true);
@@ -89,13 +91,13 @@ describe('FileClient', () => {
89
91
  path: '/root/secure'
90
92
  };
91
93
 
92
- mockFetch.mockResolvedValue(new Response(
93
- JSON.stringify(errorResponse),
94
- { status: 403 }
95
- ));
94
+ mockFetch.mockResolvedValue(
95
+ new Response(JSON.stringify(errorResponse), { status: 403 })
96
+ );
96
97
 
97
- await expect(client.mkdir('/root/secure', 'session-mkdir'))
98
- .rejects.toThrow(PermissionDeniedError);
98
+ await expect(
99
+ client.mkdir('/root/secure', 'session-mkdir')
100
+ ).rejects.toThrow(PermissionDeniedError);
99
101
  });
100
102
 
101
103
  it('should handle directory already exists errors', async () => {
@@ -105,13 +107,13 @@ describe('FileClient', () => {
105
107
  path: '/app/existing'
106
108
  };
107
109
 
108
- mockFetch.mockResolvedValue(new Response(
109
- JSON.stringify(errorResponse),
110
- { status: 409 }
111
- ));
110
+ mockFetch.mockResolvedValue(
111
+ new Response(JSON.stringify(errorResponse), { status: 409 })
112
+ );
112
113
 
113
- await expect(client.mkdir('/app/existing', 'session-mkdir'))
114
- .rejects.toThrow(FileExistsError);
114
+ await expect(
115
+ client.mkdir('/app/existing', 'session-mkdir')
116
+ ).rejects.toThrow(FileExistsError);
115
117
  });
116
118
  });
117
119
 
@@ -121,16 +123,19 @@ describe('FileClient', () => {
121
123
  success: true,
122
124
  exitCode: 0,
123
125
  path: '/app/config.json',
124
- timestamp: '2023-01-01T00:00:00Z',
126
+ timestamp: '2023-01-01T00:00:00Z'
125
127
  };
126
128
 
127
- mockFetch.mockResolvedValue(new Response(
128
- JSON.stringify(mockResponse),
129
- { status: 200 }
130
- ));
129
+ mockFetch.mockResolvedValue(
130
+ new Response(JSON.stringify(mockResponse), { status: 200 })
131
+ );
131
132
 
132
133
  const content = '{"setting": "value", "enabled": true}';
133
- const result = await client.writeFile('/app/config.json', content, 'session-write');
134
+ const result = await client.writeFile(
135
+ '/app/config.json',
136
+ content,
137
+ 'session-write'
138
+ );
134
139
 
135
140
  expect(result.success).toBe(true);
136
141
  expect(result.path).toBe('/app/config.json');
@@ -142,16 +147,21 @@ describe('FileClient', () => {
142
147
  success: true,
143
148
  exitCode: 0,
144
149
  path: '/app/image.png',
145
- timestamp: '2023-01-01T00:00:00Z',
150
+ timestamp: '2023-01-01T00:00:00Z'
146
151
  };
147
152
 
148
- mockFetch.mockResolvedValue(new Response(
149
- JSON.stringify(mockResponse),
150
- { status: 200 }
151
- ));
153
+ mockFetch.mockResolvedValue(
154
+ new Response(JSON.stringify(mockResponse), { status: 200 })
155
+ );
152
156
 
153
- const binaryData = 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChAI9jYlkKQAAAABJRU5ErkJggg==';
154
- const result = await client.writeFile('/app/image.png', binaryData, 'session-write', { encoding: 'base64' });
157
+ const binaryData =
158
+ 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChAI9jYlkKQAAAABJRU5ErkJggg==';
159
+ const result = await client.writeFile(
160
+ '/app/image.png',
161
+ binaryData,
162
+ 'session-write',
163
+ { encoding: 'base64' }
164
+ );
155
165
 
156
166
  expect(result.success).toBe(true);
157
167
  expect(result.path).toBe('/app/image.png');
@@ -164,13 +174,13 @@ describe('FileClient', () => {
164
174
  path: '/system/readonly.txt'
165
175
  };
166
176
 
167
- mockFetch.mockResolvedValue(new Response(
168
- JSON.stringify(errorResponse),
169
- { status: 403 }
170
- ));
177
+ mockFetch.mockResolvedValue(
178
+ new Response(JSON.stringify(errorResponse), { status: 403 })
179
+ );
171
180
 
172
- await expect(client.writeFile('/system/readonly.txt', 'content', 'session-err'))
173
- .rejects.toThrow(PermissionDeniedError);
181
+ await expect(
182
+ client.writeFile('/system/readonly.txt', 'content', 'session-err')
183
+ ).rejects.toThrow(PermissionDeniedError);
174
184
  });
175
185
 
176
186
  it('should handle disk space errors', async () => {
@@ -180,13 +190,17 @@ describe('FileClient', () => {
180
190
  path: '/app/largefile.dat'
181
191
  };
182
192
 
183
- mockFetch.mockResolvedValue(new Response(
184
- JSON.stringify(errorResponse),
185
- { status: 507 }
186
- ));
193
+ mockFetch.mockResolvedValue(
194
+ new Response(JSON.stringify(errorResponse), { status: 507 })
195
+ );
187
196
 
188
- await expect(client.writeFile('/app/largefile.dat', 'x'.repeat(1000000), 'session-err'))
189
- .rejects.toThrow(FileSystemError);
197
+ await expect(
198
+ client.writeFile(
199
+ '/app/largefile.dat',
200
+ 'x'.repeat(1000000),
201
+ 'session-err'
202
+ )
203
+ ).rejects.toThrow(FileSystemError);
190
204
  });
191
205
  });
192
206
 
@@ -208,13 +222,12 @@ database:
208
222
  encoding: 'utf-8',
209
223
  isBinary: false,
210
224
  mimeType: 'text/yaml',
211
- size: 100,
225
+ size: 100
212
226
  };
213
227
 
214
- mockFetch.mockResolvedValue(new Response(
215
- JSON.stringify(mockResponse),
216
- { status: 200 }
217
- ));
228
+ mockFetch.mockResolvedValue(
229
+ new Response(JSON.stringify(mockResponse), { status: 200 })
230
+ );
218
231
 
219
232
  const result = await client.readFile('/app/config.yaml', 'session-read');
220
233
 
@@ -230,7 +243,8 @@ database:
230
243
  });
231
244
 
232
245
  it('should read binary files with base64 encoding and metadata', async () => {
233
- const binaryContent = 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChAI9jYlkKQAAAABJRU5ErkJggg==';
246
+ const binaryContent =
247
+ 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChAI9jYlkKQAAAABJRU5ErkJggg==';
234
248
  const mockResponse: ReadFileResult = {
235
249
  success: true,
236
250
  exitCode: 0,
@@ -240,15 +254,16 @@ database:
240
254
  encoding: 'base64',
241
255
  isBinary: true,
242
256
  mimeType: 'image/png',
243
- size: 95,
257
+ size: 95
244
258
  };
245
259
 
246
- mockFetch.mockResolvedValue(new Response(
247
- JSON.stringify(mockResponse),
248
- { status: 200 }
249
- ));
260
+ mockFetch.mockResolvedValue(
261
+ new Response(JSON.stringify(mockResponse), { status: 200 })
262
+ );
250
263
 
251
- const result = await client.readFile('/app/logo.png', 'session-read', { encoding: 'base64' });
264
+ const result = await client.readFile('/app/logo.png', 'session-read', {
265
+ encoding: 'base64'
266
+ });
252
267
 
253
268
  expect(result.success).toBe(true);
254
269
  expect(result.content).toBe(binaryContent);
@@ -266,13 +281,13 @@ database:
266
281
  path: '/app/missing.txt'
267
282
  };
268
283
 
269
- mockFetch.mockResolvedValue(new Response(
270
- JSON.stringify(errorResponse),
271
- { status: 404 }
272
- ));
284
+ mockFetch.mockResolvedValue(
285
+ new Response(JSON.stringify(errorResponse), { status: 404 })
286
+ );
273
287
 
274
- await expect(client.readFile('/app/missing.txt', 'session-read'))
275
- .rejects.toThrow(FileNotFoundError);
288
+ await expect(
289
+ client.readFile('/app/missing.txt', 'session-read')
290
+ ).rejects.toThrow(FileNotFoundError);
276
291
  });
277
292
 
278
293
  it('should handle directory read attempts', async () => {
@@ -282,13 +297,13 @@ database:
282
297
  path: '/app/logs'
283
298
  };
284
299
 
285
- mockFetch.mockResolvedValue(new Response(
286
- JSON.stringify(errorResponse),
287
- { status: 400 }
288
- ));
300
+ mockFetch.mockResolvedValue(
301
+ new Response(JSON.stringify(errorResponse), { status: 400 })
302
+ );
289
303
 
290
- await expect(client.readFile('/app/logs', 'session-read'))
291
- .rejects.toThrow(FileSystemError);
304
+ await expect(
305
+ client.readFile('/app/logs', 'session-read')
306
+ ).rejects.toThrow(FileSystemError);
292
307
  });
293
308
  });
294
309
 
@@ -296,19 +311,36 @@ database:
296
311
  it('should stream file successfully', async () => {
297
312
  const mockStream = new ReadableStream({
298
313
  start(controller) {
299
- controller.enqueue(new TextEncoder().encode('data: {"type":"metadata","mimeType":"text/plain","size":100,"isBinary":false,"encoding":"utf-8"}\n\n'));
300
- controller.enqueue(new TextEncoder().encode('data: {"type":"chunk","data":"Hello"}\n\n'));
301
- controller.enqueue(new TextEncoder().encode('data: {"type":"complete","bytesRead":5}\n\n'));
314
+ controller.enqueue(
315
+ new TextEncoder().encode(
316
+ 'data: {"type":"metadata","mimeType":"text/plain","size":100,"isBinary":false,"encoding":"utf-8"}\n\n'
317
+ )
318
+ );
319
+ controller.enqueue(
320
+ new TextEncoder().encode(
321
+ 'data: {"type":"chunk","data":"Hello"}\n\n'
322
+ )
323
+ );
324
+ controller.enqueue(
325
+ new TextEncoder().encode(
326
+ 'data: {"type":"complete","bytesRead":5}\n\n'
327
+ )
328
+ );
302
329
  controller.close();
303
330
  }
304
331
  });
305
332
 
306
- mockFetch.mockResolvedValue(new Response(mockStream, {
307
- status: 200,
308
- headers: { 'Content-Type': 'text/event-stream' }
309
- }));
333
+ mockFetch.mockResolvedValue(
334
+ new Response(mockStream, {
335
+ status: 200,
336
+ headers: { 'Content-Type': 'text/event-stream' }
337
+ })
338
+ );
310
339
 
311
- const result = await client.readFileStream('/app/test.txt', 'session-stream');
340
+ const result = await client.readFileStream(
341
+ '/app/test.txt',
342
+ 'session-stream'
343
+ );
312
344
 
313
345
  expect(result).toBeInstanceOf(ReadableStream);
314
346
  expect(mockFetch).toHaveBeenCalledWith(
@@ -317,7 +349,7 @@ database:
317
349
  method: 'POST',
318
350
  body: JSON.stringify({
319
351
  path: '/app/test.txt',
320
- sessionId: 'session-stream',
352
+ sessionId: 'session-stream'
321
353
  })
322
354
  })
323
355
  );
@@ -326,19 +358,36 @@ database:
326
358
  it('should handle binary file streams', async () => {
327
359
  const mockStream = new ReadableStream({
328
360
  start(controller) {
329
- controller.enqueue(new TextEncoder().encode('data: {"type":"metadata","mimeType":"image/png","size":1024,"isBinary":true,"encoding":"base64"}\n\n'));
330
- controller.enqueue(new TextEncoder().encode('data: {"type":"chunk","data":"iVBORw0K"}\n\n'));
331
- controller.enqueue(new TextEncoder().encode('data: {"type":"complete","bytesRead":1024}\n\n'));
361
+ controller.enqueue(
362
+ new TextEncoder().encode(
363
+ 'data: {"type":"metadata","mimeType":"image/png","size":1024,"isBinary":true,"encoding":"base64"}\n\n'
364
+ )
365
+ );
366
+ controller.enqueue(
367
+ new TextEncoder().encode(
368
+ 'data: {"type":"chunk","data":"iVBORw0K"}\n\n'
369
+ )
370
+ );
371
+ controller.enqueue(
372
+ new TextEncoder().encode(
373
+ 'data: {"type":"complete","bytesRead":1024}\n\n'
374
+ )
375
+ );
332
376
  controller.close();
333
377
  }
334
378
  });
335
379
 
336
- mockFetch.mockResolvedValue(new Response(mockStream, {
337
- status: 200,
338
- headers: { 'Content-Type': 'text/event-stream' }
339
- }));
380
+ mockFetch.mockResolvedValue(
381
+ new Response(mockStream, {
382
+ status: 200,
383
+ headers: { 'Content-Type': 'text/event-stream' }
384
+ })
385
+ );
340
386
 
341
- const result = await client.readFileStream('/app/image.png', 'session-stream');
387
+ const result = await client.readFileStream(
388
+ '/app/image.png',
389
+ 'session-stream'
390
+ );
342
391
 
343
392
  expect(result).toBeInstanceOf(ReadableStream);
344
393
  });
@@ -350,20 +399,21 @@ database:
350
399
  path: '/app/missing.txt'
351
400
  };
352
401
 
353
- mockFetch.mockResolvedValue(new Response(
354
- JSON.stringify(errorResponse),
355
- { status: 404 }
356
- ));
402
+ mockFetch.mockResolvedValue(
403
+ new Response(JSON.stringify(errorResponse), { status: 404 })
404
+ );
357
405
 
358
- await expect(client.readFileStream('/app/missing.txt', 'session-stream'))
359
- .rejects.toThrow(FileNotFoundError);
406
+ await expect(
407
+ client.readFileStream('/app/missing.txt', 'session-stream')
408
+ ).rejects.toThrow(FileNotFoundError);
360
409
  });
361
410
 
362
411
  it('should handle network errors during streaming', async () => {
363
412
  mockFetch.mockRejectedValue(new Error('Network timeout'));
364
413
 
365
- await expect(client.readFileStream('/app/file.txt', 'session-stream'))
366
- .rejects.toThrow('Network timeout');
414
+ await expect(
415
+ client.readFileStream('/app/file.txt', 'session-stream')
416
+ ).rejects.toThrow('Network timeout');
367
417
  });
368
418
  });
369
419
 
@@ -373,13 +423,12 @@ database:
373
423
  success: true,
374
424
  exitCode: 0,
375
425
  path: '/app/temp.txt',
376
- timestamp: '2023-01-01T00:00:00Z',
426
+ timestamp: '2023-01-01T00:00:00Z'
377
427
  };
378
428
 
379
- mockFetch.mockResolvedValue(new Response(
380
- JSON.stringify(mockResponse),
381
- { status: 200 }
382
- ));
429
+ mockFetch.mockResolvedValue(
430
+ new Response(JSON.stringify(mockResponse), { status: 200 })
431
+ );
383
432
 
384
433
  const result = await client.deleteFile('/app/temp.txt', 'session-delete');
385
434
 
@@ -395,13 +444,13 @@ database:
395
444
  path: '/app/nonexistent.txt'
396
445
  };
397
446
 
398
- mockFetch.mockResolvedValue(new Response(
399
- JSON.stringify(errorResponse),
400
- { status: 404 }
401
- ));
447
+ mockFetch.mockResolvedValue(
448
+ new Response(JSON.stringify(errorResponse), { status: 404 })
449
+ );
402
450
 
403
- await expect(client.deleteFile('/app/nonexistent.txt', 'session-delete'))
404
- .rejects.toThrow(FileNotFoundError);
451
+ await expect(
452
+ client.deleteFile('/app/nonexistent.txt', 'session-delete')
453
+ ).rejects.toThrow(FileNotFoundError);
405
454
  });
406
455
 
407
456
  it('should handle delete permission errors', async () => {
@@ -411,13 +460,13 @@ database:
411
460
  path: '/system/important.conf'
412
461
  };
413
462
 
414
- mockFetch.mockResolvedValue(new Response(
415
- JSON.stringify(errorResponse),
416
- { status: 403 }
417
- ));
463
+ mockFetch.mockResolvedValue(
464
+ new Response(JSON.stringify(errorResponse), { status: 403 })
465
+ );
418
466
 
419
- await expect(client.deleteFile('/system/important.conf', 'session-delete'))
420
- .rejects.toThrow(PermissionDeniedError);
467
+ await expect(
468
+ client.deleteFile('/system/important.conf', 'session-delete')
469
+ ).rejects.toThrow(PermissionDeniedError);
421
470
  });
422
471
  });
423
472
 
@@ -428,15 +477,18 @@ database:
428
477
  exitCode: 0,
429
478
  path: '/app/old-name.txt',
430
479
  newPath: '/app/new-name.txt',
431
- timestamp: '2023-01-01T00:00:00Z',
480
+ timestamp: '2023-01-01T00:00:00Z'
432
481
  };
433
482
 
434
- mockFetch.mockResolvedValue(new Response(
435
- JSON.stringify(mockResponse),
436
- { status: 200 }
437
- ));
483
+ mockFetch.mockResolvedValue(
484
+ new Response(JSON.stringify(mockResponse), { status: 200 })
485
+ );
438
486
 
439
- const result = await client.renameFile('/app/old-name.txt', '/app/new-name.txt', 'session-rename');
487
+ const result = await client.renameFile(
488
+ '/app/old-name.txt',
489
+ '/app/new-name.txt',
490
+ 'session-rename'
491
+ );
440
492
 
441
493
  expect(result.success).toBe(true);
442
494
  expect(result.path).toBe('/app/old-name.txt');
@@ -451,13 +503,17 @@ database:
451
503
  path: '/app/existing.txt'
452
504
  };
453
505
 
454
- mockFetch.mockResolvedValue(new Response(
455
- JSON.stringify(errorResponse),
456
- { status: 409 }
457
- ));
506
+ mockFetch.mockResolvedValue(
507
+ new Response(JSON.stringify(errorResponse), { status: 409 })
508
+ );
458
509
 
459
- await expect(client.renameFile('/app/source.txt', '/app/existing.txt', 'session-rename'))
460
- .rejects.toThrow(FileExistsError);
510
+ await expect(
511
+ client.renameFile(
512
+ '/app/source.txt',
513
+ '/app/existing.txt',
514
+ 'session-rename'
515
+ )
516
+ ).rejects.toThrow(FileExistsError);
461
517
  });
462
518
  });
463
519
 
@@ -468,15 +524,18 @@ database:
468
524
  exitCode: 0,
469
525
  path: '/src/document.pdf',
470
526
  newPath: '/dest/document.pdf',
471
- timestamp: '2023-01-01T00:00:00Z',
527
+ timestamp: '2023-01-01T00:00:00Z'
472
528
  };
473
529
 
474
- mockFetch.mockResolvedValue(new Response(
475
- JSON.stringify(mockResponse),
476
- { status: 200 }
477
- ));
530
+ mockFetch.mockResolvedValue(
531
+ new Response(JSON.stringify(mockResponse), { status: 200 })
532
+ );
478
533
 
479
- const result = await client.moveFile('/src/document.pdf', '/dest/document.pdf', 'session-move');
534
+ const result = await client.moveFile(
535
+ '/src/document.pdf',
536
+ '/dest/document.pdf',
537
+ 'session-move'
538
+ );
480
539
 
481
540
  expect(result.success).toBe(true);
482
541
  expect(result.path).toBe('/src/document.pdf');
@@ -491,13 +550,17 @@ database:
491
550
  path: '/nonexistent/'
492
551
  };
493
552
 
494
- mockFetch.mockResolvedValue(new Response(
495
- JSON.stringify(errorResponse),
496
- { status: 404 }
497
- ));
553
+ mockFetch.mockResolvedValue(
554
+ new Response(JSON.stringify(errorResponse), { status: 404 })
555
+ );
498
556
 
499
- await expect(client.moveFile('/app/file.txt', '/nonexistent/file.txt', 'session-move'))
500
- .rejects.toThrow(FileSystemError);
557
+ await expect(
558
+ client.moveFile(
559
+ '/app/file.txt',
560
+ '/nonexistent/file.txt',
561
+ 'session-move'
562
+ )
563
+ ).rejects.toThrow(FileSystemError);
501
564
  });
502
565
  });
503
566
 
@@ -511,7 +574,7 @@ database:
511
574
  modifiedAt: '2023-01-01T00:00:00Z',
512
575
  mode: 'rw-r--r--',
513
576
  permissions: { readable: true, writable: true, executable: false },
514
- ...overrides,
577
+ ...overrides
515
578
  });
516
579
 
517
580
  it('should list files with correct structure', async () => {
@@ -520,13 +583,15 @@ database:
520
583
  path: '/workspace',
521
584
  files: [
522
585
  createMockFile({ name: 'file.txt' }),
523
- createMockFile({ name: 'dir', type: 'directory', mode: 'rwxr-xr-x' }),
586
+ createMockFile({ name: 'dir', type: 'directory', mode: 'rwxr-xr-x' })
524
587
  ],
525
588
  count: 2,
526
- timestamp: '2023-01-01T00:00:00Z',
589
+ timestamp: '2023-01-01T00:00:00Z'
527
590
  };
528
591
 
529
- mockFetch.mockResolvedValue(new Response(JSON.stringify(mockResponse), { status: 200 }));
592
+ mockFetch.mockResolvedValue(
593
+ new Response(JSON.stringify(mockResponse), { status: 200 })
594
+ );
530
595
 
531
596
  const result = await client.listFiles('/workspace', 'session-list');
532
597
 
@@ -543,12 +608,17 @@ database:
543
608
  path: '/workspace',
544
609
  files: [createMockFile({ name: '.hidden', relativePath: '.hidden' })],
545
610
  count: 1,
546
- timestamp: '2023-01-01T00:00:00Z',
611
+ timestamp: '2023-01-01T00:00:00Z'
547
612
  };
548
613
 
549
- mockFetch.mockResolvedValue(new Response(JSON.stringify(mockResponse), { status: 200 }));
614
+ mockFetch.mockResolvedValue(
615
+ new Response(JSON.stringify(mockResponse), { status: 200 })
616
+ );
550
617
 
551
- await client.listFiles('/workspace', 'session-list', { recursive: true, includeHidden: true });
618
+ await client.listFiles('/workspace', 'session-list', {
619
+ recursive: true,
620
+ includeHidden: true
621
+ });
552
622
 
553
623
  expect(mockFetch).toHaveBeenCalledWith(
554
624
  expect.stringContaining('/api/list-files'),
@@ -556,17 +626,25 @@ database:
556
626
  body: JSON.stringify({
557
627
  path: '/workspace',
558
628
  sessionId: 'session-list',
559
- options: { recursive: true, includeHidden: true },
629
+ options: { recursive: true, includeHidden: true }
560
630
  })
561
631
  })
562
632
  );
563
633
  });
564
634
 
565
635
  it('should handle empty directories', async () => {
566
- mockFetch.mockResolvedValue(new Response(
567
- JSON.stringify({ success: true, path: '/empty', files: [], count: 0, timestamp: '2023-01-01T00:00:00Z' }),
568
- { status: 200 }
569
- ));
636
+ mockFetch.mockResolvedValue(
637
+ new Response(
638
+ JSON.stringify({
639
+ success: true,
640
+ path: '/empty',
641
+ files: [],
642
+ count: 0,
643
+ timestamp: '2023-01-01T00:00:00Z'
644
+ }),
645
+ { status: 200 }
646
+ )
647
+ );
570
648
 
571
649
  const result = await client.listFiles('/empty', 'session-list');
572
650
 
@@ -575,13 +653,19 @@ database:
575
653
  });
576
654
 
577
655
  it('should handle error responses', async () => {
578
- mockFetch.mockResolvedValue(new Response(
579
- JSON.stringify({ error: 'Directory not found', code: 'FILE_NOT_FOUND' }),
580
- { status: 404 }
581
- ));
656
+ mockFetch.mockResolvedValue(
657
+ new Response(
658
+ JSON.stringify({
659
+ error: 'Directory not found',
660
+ code: 'FILE_NOT_FOUND'
661
+ }),
662
+ { status: 404 }
663
+ )
664
+ );
582
665
 
583
- await expect(client.listFiles('/nonexistent', 'session-list'))
584
- .rejects.toThrow(FileNotFoundError);
666
+ await expect(
667
+ client.listFiles('/nonexistent', 'session-list')
668
+ ).rejects.toThrow(FileNotFoundError);
585
669
  });
586
670
  });
587
671
 
@@ -591,12 +675,17 @@ database:
591
675
  success: true,
592
676
  path: '/workspace/test.txt',
593
677
  exists: true,
594
- timestamp: '2023-01-01T00:00:00Z',
678
+ timestamp: '2023-01-01T00:00:00Z'
595
679
  };
596
680
 
597
- mockFetch.mockResolvedValue(new Response(JSON.stringify(mockResponse), { status: 200 }));
681
+ mockFetch.mockResolvedValue(
682
+ new Response(JSON.stringify(mockResponse), { status: 200 })
683
+ );
598
684
 
599
- const result = await client.exists('/workspace/test.txt', 'session-exists');
685
+ const result = await client.exists(
686
+ '/workspace/test.txt',
687
+ 'session-exists'
688
+ );
600
689
 
601
690
  expect(result.success).toBe(true);
602
691
  expect(result.exists).toBe(true);
@@ -608,12 +697,17 @@ database:
608
697
  success: true,
609
698
  path: '/workspace/nonexistent.txt',
610
699
  exists: false,
611
- timestamp: '2023-01-01T00:00:00Z',
700
+ timestamp: '2023-01-01T00:00:00Z'
612
701
  };
613
702
 
614
- mockFetch.mockResolvedValue(new Response(JSON.stringify(mockResponse), { status: 200 }));
703
+ mockFetch.mockResolvedValue(
704
+ new Response(JSON.stringify(mockResponse), { status: 200 })
705
+ );
615
706
 
616
- const result = await client.exists('/workspace/nonexistent.txt', 'session-exists');
707
+ const result = await client.exists(
708
+ '/workspace/nonexistent.txt',
709
+ 'session-exists'
710
+ );
617
711
 
618
712
  expect(result.success).toBe(true);
619
713
  expect(result.exists).toBe(false);
@@ -624,12 +718,17 @@ database:
624
718
  success: true,
625
719
  path: '/workspace/some-dir',
626
720
  exists: true,
627
- timestamp: '2023-01-01T00:00:00Z',
721
+ timestamp: '2023-01-01T00:00:00Z'
628
722
  };
629
723
 
630
- mockFetch.mockResolvedValue(new Response(JSON.stringify(mockResponse), { status: 200 }));
724
+ mockFetch.mockResolvedValue(
725
+ new Response(JSON.stringify(mockResponse), { status: 200 })
726
+ );
631
727
 
632
- const result = await client.exists('/workspace/some-dir', 'session-exists');
728
+ const result = await client.exists(
729
+ '/workspace/some-dir',
730
+ 'session-exists'
731
+ );
633
732
 
634
733
  expect(result.success).toBe(true);
635
734
  expect(result.exists).toBe(true);
@@ -640,10 +739,12 @@ database:
640
739
  success: true,
641
740
  path: '/test/path',
642
741
  exists: true,
643
- timestamp: '2023-01-01T00:00:00Z',
742
+ timestamp: '2023-01-01T00:00:00Z'
644
743
  };
645
744
 
646
- mockFetch.mockResolvedValue(new Response(JSON.stringify(mockResponse), { status: 200 }));
745
+ mockFetch.mockResolvedValue(
746
+ new Response(JSON.stringify(mockResponse), { status: 200 })
747
+ );
647
748
 
648
749
  await client.exists('/test/path', 'session-test');
649
750
 
@@ -653,7 +754,7 @@ database:
653
754
  method: 'POST',
654
755
  body: JSON.stringify({
655
756
  path: '/test/path',
656
- sessionId: 'session-test',
757
+ sessionId: 'session-test'
657
758
  })
658
759
  })
659
760
  );
@@ -664,40 +765,51 @@ database:
664
765
  it('should handle network failures gracefully', async () => {
665
766
  mockFetch.mockRejectedValue(new Error('Network connection failed'));
666
767
 
667
- await expect(client.readFile('/app/file.txt', 'session-read'))
668
- .rejects.toThrow('Network connection failed');
768
+ await expect(
769
+ client.readFile('/app/file.txt', 'session-read')
770
+ ).rejects.toThrow('Network connection failed');
669
771
  });
670
772
 
671
773
  it('should handle malformed server responses', async () => {
672
- mockFetch.mockResolvedValue(new Response(
673
- 'invalid json {',
674
- { status: 200, headers: { 'Content-Type': 'application/json' } }
675
- ));
774
+ mockFetch.mockResolvedValue(
775
+ new Response('invalid json {', {
776
+ status: 200,
777
+ headers: { 'Content-Type': 'application/json' }
778
+ })
779
+ );
676
780
 
677
- await expect(client.writeFile('/app/file.txt', 'content', 'session-err'))
678
- .rejects.toThrow(SandboxError);
781
+ await expect(
782
+ client.writeFile('/app/file.txt', 'content', 'session-err')
783
+ ).rejects.toThrow(SandboxError);
679
784
  });
680
785
 
681
786
  it('should handle server errors with proper mapping', async () => {
682
787
  const serverErrorScenarios = [
683
788
  { status: 400, code: 'FILESYSTEM_ERROR', error: FileSystemError },
684
- { status: 403, code: 'PERMISSION_DENIED', error: PermissionDeniedError },
789
+ {
790
+ status: 403,
791
+ code: 'PERMISSION_DENIED',
792
+ error: PermissionDeniedError
793
+ },
685
794
  { status: 404, code: 'FILE_NOT_FOUND', error: FileNotFoundError },
686
795
  { status: 409, code: 'FILE_EXISTS', error: FileExistsError },
687
- { status: 500, code: 'INTERNAL_ERROR', error: SandboxError },
796
+ { status: 500, code: 'INTERNAL_ERROR', error: SandboxError }
688
797
  ];
689
798
 
690
799
  for (const scenario of serverErrorScenarios) {
691
- mockFetch.mockResolvedValueOnce(new Response(
692
- JSON.stringify({
693
- error: 'Test error',
694
- code: scenario.code
695
- }),
696
- { status: scenario.status }
697
- ));
698
-
699
- await expect(client.readFile('/app/test.txt', 'session-read'))
700
- .rejects.toThrow(scenario.error);
800
+ mockFetch.mockResolvedValueOnce(
801
+ new Response(
802
+ JSON.stringify({
803
+ error: 'Test error',
804
+ code: scenario.code
805
+ }),
806
+ { status: scenario.status }
807
+ )
808
+ );
809
+
810
+ await expect(
811
+ client.readFile('/app/test.txt', 'session-read')
812
+ ).rejects.toThrow(scenario.error);
701
813
  }
702
814
  });
703
815
  });
@@ -711,9 +823,9 @@ database:
711
823
  it('should initialize with full options', () => {
712
824
  const fullOptionsClient = new FileClient({
713
825
  baseUrl: 'http://custom.com',
714
- port: 8080,
826
+ port: 8080
715
827
  });
716
828
  expect(fullOptionsClient).toBeDefined();
717
829
  });
718
830
  });
719
- });
831
+ });