@cloudflare/sandbox 0.5.6 → 0.6.1
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.
- package/Dockerfile +54 -56
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -1
- package/dist/index.js.map +1 -1
- package/package.json +11 -6
- package/.turbo/turbo-build.log +0 -23
- package/CHANGELOG.md +0 -463
- package/src/clients/base-client.ts +0 -356
- package/src/clients/command-client.ts +0 -133
- package/src/clients/file-client.ts +0 -300
- package/src/clients/git-client.ts +0 -98
- package/src/clients/index.ts +0 -64
- package/src/clients/interpreter-client.ts +0 -339
- package/src/clients/port-client.ts +0 -105
- package/src/clients/process-client.ts +0 -198
- package/src/clients/sandbox-client.ts +0 -39
- package/src/clients/types.ts +0 -88
- package/src/clients/utility-client.ts +0 -156
- package/src/errors/adapter.ts +0 -238
- package/src/errors/classes.ts +0 -594
- package/src/errors/index.ts +0 -109
- package/src/file-stream.ts +0 -175
- package/src/index.ts +0 -121
- package/src/interpreter.ts +0 -168
- package/src/openai/index.ts +0 -465
- package/src/request-handler.ts +0 -184
- package/src/sandbox.ts +0 -1937
- package/src/security.ts +0 -119
- package/src/sse-parser.ts +0 -147
- package/src/storage-mount/credential-detection.ts +0 -41
- package/src/storage-mount/errors.ts +0 -51
- package/src/storage-mount/index.ts +0 -17
- package/src/storage-mount/provider-detection.ts +0 -93
- package/src/storage-mount/types.ts +0 -17
- package/src/version.ts +0 -6
- package/tests/base-client.test.ts +0 -582
- package/tests/command-client.test.ts +0 -444
- package/tests/file-client.test.ts +0 -831
- package/tests/file-stream.test.ts +0 -310
- package/tests/get-sandbox.test.ts +0 -172
- package/tests/git-client.test.ts +0 -455
- package/tests/openai-shell-editor.test.ts +0 -434
- package/tests/port-client.test.ts +0 -283
- package/tests/process-client.test.ts +0 -649
- package/tests/request-handler.test.ts +0 -292
- package/tests/sandbox.test.ts +0 -890
- package/tests/sse-parser.test.ts +0 -291
- package/tests/storage-mount/credential-detection.test.ts +0 -119
- package/tests/storage-mount/provider-detection.test.ts +0 -77
- package/tests/utility-client.test.ts +0 -339
- package/tests/version.test.ts +0 -16
- package/tests/wrangler.jsonc +0 -35
- package/tsconfig.json +0 -11
- package/tsdown.config.ts +0 -13
- package/vitest.config.ts +0 -31
|
@@ -1,310 +0,0 @@
|
|
|
1
|
-
import type { FileMetadata } from '@repo/shared';
|
|
2
|
-
import { describe, expect, it } from 'vitest';
|
|
3
|
-
import { collectFile, streamFile } from '../src/file-stream';
|
|
4
|
-
|
|
5
|
-
describe('File Streaming Utilities', () => {
|
|
6
|
-
/**
|
|
7
|
-
* Helper to create a mock SSE stream for testing
|
|
8
|
-
*/
|
|
9
|
-
function createMockSSEStream(events: string[]): ReadableStream<Uint8Array> {
|
|
10
|
-
return new ReadableStream({
|
|
11
|
-
start(controller) {
|
|
12
|
-
for (const event of events) {
|
|
13
|
-
controller.enqueue(new TextEncoder().encode(event));
|
|
14
|
-
}
|
|
15
|
-
controller.close();
|
|
16
|
-
}
|
|
17
|
-
});
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
describe('streamFile', () => {
|
|
21
|
-
it('should stream text file chunks and return metadata', async () => {
|
|
22
|
-
const stream = createMockSSEStream([
|
|
23
|
-
'data: {"type":"metadata","mimeType":"text/plain","size":11,"isBinary":false,"encoding":"utf-8"}\n\n',
|
|
24
|
-
'data: {"type":"chunk","data":"Hello"}\n\n',
|
|
25
|
-
'data: {"type":"chunk","data":" World"}\n\n',
|
|
26
|
-
'data: {"type":"complete","bytesRead":11}\n\n'
|
|
27
|
-
]);
|
|
28
|
-
|
|
29
|
-
const chunks: string[] = [];
|
|
30
|
-
const generator = streamFile(stream);
|
|
31
|
-
let result = await generator.next();
|
|
32
|
-
|
|
33
|
-
// Collect chunks
|
|
34
|
-
while (!result.done) {
|
|
35
|
-
chunks.push(result.value as string);
|
|
36
|
-
result = await generator.next();
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
// Metadata is the return value
|
|
40
|
-
const metadata = result.value;
|
|
41
|
-
|
|
42
|
-
expect(chunks).toEqual(['Hello', ' World']);
|
|
43
|
-
expect(metadata).toEqual({
|
|
44
|
-
mimeType: 'text/plain',
|
|
45
|
-
size: 11,
|
|
46
|
-
isBinary: false,
|
|
47
|
-
encoding: 'utf-8'
|
|
48
|
-
});
|
|
49
|
-
});
|
|
50
|
-
|
|
51
|
-
it('should stream binary file with base64 decoding', async () => {
|
|
52
|
-
// Base64 encoded "test" = "dGVzdA=="
|
|
53
|
-
const stream = createMockSSEStream([
|
|
54
|
-
'data: {"type":"metadata","mimeType":"image/png","size":4,"isBinary":true,"encoding":"base64"}\n\n',
|
|
55
|
-
'data: {"type":"chunk","data":"dGVzdA=="}\n\n',
|
|
56
|
-
'data: {"type":"complete","bytesRead":4}\n\n'
|
|
57
|
-
]);
|
|
58
|
-
|
|
59
|
-
const chunks: (string | Uint8Array)[] = [];
|
|
60
|
-
const generator = streamFile(stream);
|
|
61
|
-
let result = await generator.next();
|
|
62
|
-
|
|
63
|
-
// Collect chunks
|
|
64
|
-
while (!result.done) {
|
|
65
|
-
chunks.push(result.value);
|
|
66
|
-
result = await generator.next();
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
const metadata = result.value;
|
|
70
|
-
|
|
71
|
-
// For binary files, chunks should be Uint8Array
|
|
72
|
-
expect(chunks.length).toBeGreaterThan(0);
|
|
73
|
-
expect(chunks[0]).toBeInstanceOf(Uint8Array);
|
|
74
|
-
|
|
75
|
-
// Verify we can reconstruct the original data
|
|
76
|
-
const allBytes = new Uint8Array(
|
|
77
|
-
chunks.reduce((acc, chunk) => {
|
|
78
|
-
if (chunk instanceof Uint8Array) {
|
|
79
|
-
return acc + chunk.length;
|
|
80
|
-
}
|
|
81
|
-
return acc;
|
|
82
|
-
}, 0)
|
|
83
|
-
);
|
|
84
|
-
|
|
85
|
-
let offset = 0;
|
|
86
|
-
for (const chunk of chunks) {
|
|
87
|
-
if (chunk instanceof Uint8Array) {
|
|
88
|
-
allBytes.set(chunk, offset);
|
|
89
|
-
offset += chunk.length;
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
const decoded = new TextDecoder().decode(allBytes);
|
|
94
|
-
expect(decoded).toBe('test');
|
|
95
|
-
|
|
96
|
-
expect(metadata?.isBinary).toBe(true);
|
|
97
|
-
expect(metadata?.encoding).toBe('base64');
|
|
98
|
-
expect(metadata?.mimeType).toBe('image/png');
|
|
99
|
-
});
|
|
100
|
-
|
|
101
|
-
it('should handle empty files', async () => {
|
|
102
|
-
const stream = createMockSSEStream([
|
|
103
|
-
'data: {"type":"metadata","mimeType":"text/plain","size":0,"isBinary":false,"encoding":"utf-8"}\n\n',
|
|
104
|
-
'data: {"type":"complete","bytesRead":0}\n\n'
|
|
105
|
-
]);
|
|
106
|
-
|
|
107
|
-
const chunks: string[] = [];
|
|
108
|
-
const generator = streamFile(stream);
|
|
109
|
-
let result = await generator.next();
|
|
110
|
-
|
|
111
|
-
while (!result.done) {
|
|
112
|
-
chunks.push(result.value as string);
|
|
113
|
-
result = await generator.next();
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
const metadata = result.value;
|
|
117
|
-
|
|
118
|
-
expect(chunks).toEqual([]);
|
|
119
|
-
expect(metadata?.size).toBe(0);
|
|
120
|
-
});
|
|
121
|
-
|
|
122
|
-
it('should handle error events', async () => {
|
|
123
|
-
const stream = createMockSSEStream([
|
|
124
|
-
'data: {"type":"metadata","mimeType":"text/plain","size":100,"isBinary":false,"encoding":"utf-8"}\n\n',
|
|
125
|
-
'data: {"type":"chunk","data":"Hello"}\n\n',
|
|
126
|
-
'data: {"type":"error","error":"Read error: Permission denied"}\n\n'
|
|
127
|
-
]);
|
|
128
|
-
|
|
129
|
-
const generator = streamFile(stream);
|
|
130
|
-
|
|
131
|
-
try {
|
|
132
|
-
let result = await generator.next();
|
|
133
|
-
while (!result.done) {
|
|
134
|
-
result = await generator.next();
|
|
135
|
-
}
|
|
136
|
-
// Should have thrown
|
|
137
|
-
expect(true).toBe(false);
|
|
138
|
-
} catch (error) {
|
|
139
|
-
expect((error as Error).message).toContain(
|
|
140
|
-
'Read error: Permission denied'
|
|
141
|
-
);
|
|
142
|
-
}
|
|
143
|
-
});
|
|
144
|
-
});
|
|
145
|
-
|
|
146
|
-
describe('collectFile', () => {
|
|
147
|
-
it('should collect entire text file into string', async () => {
|
|
148
|
-
const stream = createMockSSEStream([
|
|
149
|
-
'data: {"type":"metadata","mimeType":"text/plain","size":11,"isBinary":false,"encoding":"utf-8"}\n\n',
|
|
150
|
-
'data: {"type":"chunk","data":"Hello"}\n\n',
|
|
151
|
-
'data: {"type":"chunk","data":" World"}\n\n',
|
|
152
|
-
'data: {"type":"complete","bytesRead":11}\n\n'
|
|
153
|
-
]);
|
|
154
|
-
|
|
155
|
-
const result = await collectFile(stream);
|
|
156
|
-
|
|
157
|
-
expect(result.content).toBe('Hello World');
|
|
158
|
-
expect(result.metadata).toEqual({
|
|
159
|
-
mimeType: 'text/plain',
|
|
160
|
-
size: 11,
|
|
161
|
-
isBinary: false,
|
|
162
|
-
encoding: 'utf-8'
|
|
163
|
-
});
|
|
164
|
-
});
|
|
165
|
-
|
|
166
|
-
it('should collect entire binary file into Uint8Array', async () => {
|
|
167
|
-
// Base64 encoded "test" = "dGVzdA=="
|
|
168
|
-
const stream = createMockSSEStream([
|
|
169
|
-
'data: {"type":"metadata","mimeType":"image/png","size":4,"isBinary":true,"encoding":"base64"}\n\n',
|
|
170
|
-
'data: {"type":"chunk","data":"dGVzdA=="}\n\n',
|
|
171
|
-
'data: {"type":"complete","bytesRead":4}\n\n'
|
|
172
|
-
]);
|
|
173
|
-
|
|
174
|
-
const result = await collectFile(stream);
|
|
175
|
-
|
|
176
|
-
expect(result.content).toBeInstanceOf(Uint8Array);
|
|
177
|
-
expect(result.metadata.isBinary).toBe(true);
|
|
178
|
-
|
|
179
|
-
// Decode to verify content
|
|
180
|
-
const decoded = new TextDecoder().decode(result.content as Uint8Array);
|
|
181
|
-
expect(decoded).toBe('test');
|
|
182
|
-
});
|
|
183
|
-
|
|
184
|
-
it('should handle empty files', async () => {
|
|
185
|
-
const stream = createMockSSEStream([
|
|
186
|
-
'data: {"type":"metadata","mimeType":"text/plain","size":0,"isBinary":false,"encoding":"utf-8"}\n\n',
|
|
187
|
-
'data: {"type":"complete","bytesRead":0}\n\n'
|
|
188
|
-
]);
|
|
189
|
-
|
|
190
|
-
const result = await collectFile(stream);
|
|
191
|
-
|
|
192
|
-
expect(result.content).toBe('');
|
|
193
|
-
expect(result.metadata.size).toBe(0);
|
|
194
|
-
});
|
|
195
|
-
|
|
196
|
-
it('should propagate errors from stream', async () => {
|
|
197
|
-
const stream = createMockSSEStream([
|
|
198
|
-
'data: {"type":"metadata","mimeType":"text/plain","size":100,"isBinary":false,"encoding":"utf-8"}\n\n',
|
|
199
|
-
'data: {"type":"chunk","data":"Hello"}\n\n',
|
|
200
|
-
'data: {"type":"error","error":"File not found"}\n\n'
|
|
201
|
-
]);
|
|
202
|
-
|
|
203
|
-
await expect(collectFile(stream)).rejects.toThrow('File not found');
|
|
204
|
-
});
|
|
205
|
-
|
|
206
|
-
it('should handle large text files efficiently', async () => {
|
|
207
|
-
// Create a stream with many chunks
|
|
208
|
-
const chunkCount = 100;
|
|
209
|
-
const events = [
|
|
210
|
-
'data: {"type":"metadata","mimeType":"text/plain","size":500,"isBinary":false,"encoding":"utf-8"}\n\n'
|
|
211
|
-
];
|
|
212
|
-
|
|
213
|
-
for (let i = 0; i < chunkCount; i++) {
|
|
214
|
-
events.push(`data: {"type":"chunk","data":"chunk${i}"}\n\n`);
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
events.push('data: {"type":"complete","bytesRead":500}\n\n');
|
|
218
|
-
|
|
219
|
-
const stream = createMockSSEStream(events);
|
|
220
|
-
const result = await collectFile(stream);
|
|
221
|
-
|
|
222
|
-
expect(typeof result.content).toBe('string');
|
|
223
|
-
expect(result.content).toContain('chunk0');
|
|
224
|
-
expect(result.content).toContain('chunk99');
|
|
225
|
-
expect(result.metadata.encoding).toBe('utf-8');
|
|
226
|
-
});
|
|
227
|
-
|
|
228
|
-
it('should handle large binary files efficiently', async () => {
|
|
229
|
-
// Create a stream with many base64 chunks
|
|
230
|
-
const chunkCount = 100;
|
|
231
|
-
const events = [
|
|
232
|
-
'data: {"type":"metadata","mimeType":"application/octet-stream","size":400,"isBinary":true,"encoding":"base64"}\n\n'
|
|
233
|
-
];
|
|
234
|
-
|
|
235
|
-
for (let i = 0; i < chunkCount; i++) {
|
|
236
|
-
// Each "AAAA" base64 chunk decodes to 3 bytes (0x00, 0x00, 0x00)
|
|
237
|
-
events.push('data: {"type":"chunk","data":"AAAA"}\n\n');
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
events.push('data: {"type":"complete","bytesRead":400}\n\n');
|
|
241
|
-
|
|
242
|
-
const stream = createMockSSEStream(events);
|
|
243
|
-
const result = await collectFile(stream);
|
|
244
|
-
|
|
245
|
-
expect(result.content).toBeInstanceOf(Uint8Array);
|
|
246
|
-
expect((result.content as Uint8Array).length).toBeGreaterThan(0);
|
|
247
|
-
expect(result.metadata.isBinary).toBe(true);
|
|
248
|
-
});
|
|
249
|
-
});
|
|
250
|
-
|
|
251
|
-
describe('edge cases', () => {
|
|
252
|
-
it('should handle streams with no metadata event', async () => {
|
|
253
|
-
const stream = createMockSSEStream([
|
|
254
|
-
'data: {"type":"chunk","data":"Hello"}\n\n',
|
|
255
|
-
'data: {"type":"complete","bytesRead":5}\n\n'
|
|
256
|
-
]);
|
|
257
|
-
|
|
258
|
-
// Without metadata, we don't know if it's binary or text
|
|
259
|
-
// The implementation should throw
|
|
260
|
-
const generator = streamFile(stream);
|
|
261
|
-
|
|
262
|
-
try {
|
|
263
|
-
let result = await generator.next();
|
|
264
|
-
while (!result.done) {
|
|
265
|
-
result = await generator.next();
|
|
266
|
-
}
|
|
267
|
-
// Should have thrown
|
|
268
|
-
expect(true).toBe(false);
|
|
269
|
-
} catch (error) {
|
|
270
|
-
expect((error as Error).message).toContain(
|
|
271
|
-
'Received chunk before metadata'
|
|
272
|
-
);
|
|
273
|
-
}
|
|
274
|
-
});
|
|
275
|
-
|
|
276
|
-
it('should handle malformed JSON in SSE events', async () => {
|
|
277
|
-
const stream = createMockSSEStream([
|
|
278
|
-
'data: {"type":"metadata","mimeType":"text/plain","size":5,"isBinary":false,"encoding":"utf-8"}\n\n',
|
|
279
|
-
'data: {invalid json\n\n',
|
|
280
|
-
'data: {"type":"complete","bytesRead":5}\n\n'
|
|
281
|
-
]);
|
|
282
|
-
|
|
283
|
-
// Malformed JSON is logged but doesn't break the stream
|
|
284
|
-
// It should complete successfully but with no chunks
|
|
285
|
-
const result = await collectFile(stream);
|
|
286
|
-
expect(result.content).toBe('');
|
|
287
|
-
});
|
|
288
|
-
|
|
289
|
-
it('should handle base64 padding correctly', async () => {
|
|
290
|
-
// Test various base64 strings with different padding
|
|
291
|
-
const testCases = [
|
|
292
|
-
{ input: 'YQ==', expected: 'a' }, // 1 byte, 2 padding
|
|
293
|
-
{ input: 'YWI=', expected: 'ab' }, // 2 bytes, 1 padding
|
|
294
|
-
{ input: 'YWJj', expected: 'abc' } // 3 bytes, no padding
|
|
295
|
-
];
|
|
296
|
-
|
|
297
|
-
for (const testCase of testCases) {
|
|
298
|
-
const stream = createMockSSEStream([
|
|
299
|
-
`data: {"type":"metadata","mimeType":"application/octet-stream","size":${testCase.expected.length},"isBinary":true,"encoding":"base64"}\n\n`,
|
|
300
|
-
`data: {"type":"chunk","data":"${testCase.input}"}\n\n`,
|
|
301
|
-
`data: {"type":"complete","bytesRead":${testCase.expected.length}}\n\n`
|
|
302
|
-
]);
|
|
303
|
-
|
|
304
|
-
const result = await collectFile(stream);
|
|
305
|
-
const decoded = new TextDecoder().decode(result.content as Uint8Array);
|
|
306
|
-
expect(decoded).toBe(testCase.expected);
|
|
307
|
-
}
|
|
308
|
-
});
|
|
309
|
-
});
|
|
310
|
-
});
|
|
@@ -1,172 +0,0 @@
|
|
|
1
|
-
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
|
2
|
-
import { getSandbox } from '../src/sandbox';
|
|
3
|
-
|
|
4
|
-
// Mock the Container module
|
|
5
|
-
vi.mock('@cloudflare/containers', () => ({
|
|
6
|
-
Container: class Container {
|
|
7
|
-
ctx: any;
|
|
8
|
-
env: any;
|
|
9
|
-
sleepAfter: string | number = '10m';
|
|
10
|
-
constructor(ctx: any, env: any) {
|
|
11
|
-
this.ctx = ctx;
|
|
12
|
-
this.env = env;
|
|
13
|
-
}
|
|
14
|
-
},
|
|
15
|
-
getContainer: vi.fn()
|
|
16
|
-
}));
|
|
17
|
-
|
|
18
|
-
describe('getSandbox', () => {
|
|
19
|
-
let mockStub: any;
|
|
20
|
-
let mockGetContainer: any;
|
|
21
|
-
|
|
22
|
-
beforeEach(async () => {
|
|
23
|
-
vi.clearAllMocks();
|
|
24
|
-
|
|
25
|
-
// Create a fresh mock stub for each test
|
|
26
|
-
mockStub = {
|
|
27
|
-
sleepAfter: '10m',
|
|
28
|
-
setSandboxName: vi.fn(),
|
|
29
|
-
setBaseUrl: vi.fn(),
|
|
30
|
-
setSleepAfter: vi.fn((value: string | number) => {
|
|
31
|
-
mockStub.sleepAfter = value;
|
|
32
|
-
}),
|
|
33
|
-
setKeepAlive: vi.fn()
|
|
34
|
-
};
|
|
35
|
-
|
|
36
|
-
// Mock getContainer to return our stub
|
|
37
|
-
const containers = await import('@cloudflare/containers');
|
|
38
|
-
mockGetContainer = vi.mocked(containers.getContainer);
|
|
39
|
-
mockGetContainer.mockReturnValue(mockStub);
|
|
40
|
-
});
|
|
41
|
-
|
|
42
|
-
it('should create a sandbox instance with default sleepAfter', () => {
|
|
43
|
-
const mockNamespace = {} as any;
|
|
44
|
-
const sandbox = getSandbox(mockNamespace, 'test-sandbox');
|
|
45
|
-
|
|
46
|
-
expect(sandbox).toBeDefined();
|
|
47
|
-
expect(sandbox.setSandboxName).toHaveBeenCalledWith(
|
|
48
|
-
'test-sandbox',
|
|
49
|
-
undefined
|
|
50
|
-
);
|
|
51
|
-
});
|
|
52
|
-
|
|
53
|
-
it('should apply sleepAfter option when provided as string', () => {
|
|
54
|
-
const mockNamespace = {} as any;
|
|
55
|
-
const sandbox = getSandbox(mockNamespace, 'test-sandbox', {
|
|
56
|
-
sleepAfter: '5m'
|
|
57
|
-
});
|
|
58
|
-
|
|
59
|
-
expect(sandbox.sleepAfter).toBe('5m');
|
|
60
|
-
});
|
|
61
|
-
|
|
62
|
-
it('should apply sleepAfter option when provided as number', () => {
|
|
63
|
-
const mockNamespace = {} as any;
|
|
64
|
-
const sandbox = getSandbox(mockNamespace, 'test-sandbox', {
|
|
65
|
-
sleepAfter: 300 // 5 minutes in seconds
|
|
66
|
-
});
|
|
67
|
-
|
|
68
|
-
expect(sandbox.sleepAfter).toBe(300);
|
|
69
|
-
});
|
|
70
|
-
|
|
71
|
-
it('should apply baseUrl option when provided', () => {
|
|
72
|
-
const mockNamespace = {} as any;
|
|
73
|
-
const sandbox = getSandbox(mockNamespace, 'test-sandbox', {
|
|
74
|
-
baseUrl: 'https://example.com'
|
|
75
|
-
});
|
|
76
|
-
|
|
77
|
-
expect(sandbox.setBaseUrl).toHaveBeenCalledWith('https://example.com');
|
|
78
|
-
});
|
|
79
|
-
|
|
80
|
-
it('should apply both sleepAfter and baseUrl options together', () => {
|
|
81
|
-
const mockNamespace = {} as any;
|
|
82
|
-
const sandbox = getSandbox(mockNamespace, 'test-sandbox', {
|
|
83
|
-
sleepAfter: '10m',
|
|
84
|
-
baseUrl: 'https://example.com'
|
|
85
|
-
});
|
|
86
|
-
|
|
87
|
-
expect(sandbox.sleepAfter).toBe('10m');
|
|
88
|
-
expect(sandbox.setBaseUrl).toHaveBeenCalledWith('https://example.com');
|
|
89
|
-
});
|
|
90
|
-
|
|
91
|
-
it('should not apply sleepAfter when not provided', () => {
|
|
92
|
-
const mockNamespace = {} as any;
|
|
93
|
-
const sandbox = getSandbox(mockNamespace, 'test-sandbox');
|
|
94
|
-
|
|
95
|
-
// Should remain default value from Container
|
|
96
|
-
expect(sandbox.sleepAfter).toBe('10m');
|
|
97
|
-
});
|
|
98
|
-
|
|
99
|
-
it('should accept various time string formats for sleepAfter', () => {
|
|
100
|
-
const mockNamespace = {} as any;
|
|
101
|
-
const testCases = ['30s', '1m', '10m', '1h', '2h'];
|
|
102
|
-
|
|
103
|
-
for (const timeString of testCases) {
|
|
104
|
-
// Reset the mock stub for each iteration
|
|
105
|
-
mockStub.sleepAfter = '3m';
|
|
106
|
-
|
|
107
|
-
const sandbox = getSandbox(mockNamespace, `test-sandbox-${timeString}`, {
|
|
108
|
-
sleepAfter: timeString
|
|
109
|
-
});
|
|
110
|
-
|
|
111
|
-
expect(sandbox.sleepAfter).toBe(timeString);
|
|
112
|
-
}
|
|
113
|
-
});
|
|
114
|
-
|
|
115
|
-
it('should apply keepAlive option when provided as true', () => {
|
|
116
|
-
const mockNamespace = {} as any;
|
|
117
|
-
const sandbox = getSandbox(mockNamespace, 'test-sandbox', {
|
|
118
|
-
keepAlive: true
|
|
119
|
-
});
|
|
120
|
-
|
|
121
|
-
expect(sandbox.setKeepAlive).toHaveBeenCalledWith(true);
|
|
122
|
-
});
|
|
123
|
-
|
|
124
|
-
it('should apply keepAlive option when provided as false', () => {
|
|
125
|
-
const mockNamespace = {} as any;
|
|
126
|
-
const sandbox = getSandbox(mockNamespace, 'test-sandbox', {
|
|
127
|
-
keepAlive: false
|
|
128
|
-
});
|
|
129
|
-
|
|
130
|
-
expect(sandbox.setKeepAlive).toHaveBeenCalledWith(false);
|
|
131
|
-
});
|
|
132
|
-
|
|
133
|
-
it('should not call setKeepAlive when keepAlive option not provided', () => {
|
|
134
|
-
const mockNamespace = {} as any;
|
|
135
|
-
getSandbox(mockNamespace, 'test-sandbox');
|
|
136
|
-
|
|
137
|
-
expect(mockStub.setKeepAlive).not.toHaveBeenCalled();
|
|
138
|
-
});
|
|
139
|
-
|
|
140
|
-
it('should apply keepAlive alongside other options', () => {
|
|
141
|
-
const mockNamespace = {} as any;
|
|
142
|
-
const sandbox = getSandbox(mockNamespace, 'test-sandbox', {
|
|
143
|
-
sleepAfter: '5m',
|
|
144
|
-
baseUrl: 'https://example.com',
|
|
145
|
-
keepAlive: true
|
|
146
|
-
});
|
|
147
|
-
|
|
148
|
-
expect(sandbox.sleepAfter).toBe('5m');
|
|
149
|
-
expect(sandbox.setBaseUrl).toHaveBeenCalledWith('https://example.com');
|
|
150
|
-
expect(sandbox.setKeepAlive).toHaveBeenCalledWith(true);
|
|
151
|
-
});
|
|
152
|
-
|
|
153
|
-
it('should preserve sandbox ID case by default', () => {
|
|
154
|
-
const mockNamespace = {} as any;
|
|
155
|
-
getSandbox(mockNamespace, 'MyProject-ABC123');
|
|
156
|
-
|
|
157
|
-
expect(mockGetContainer).toHaveBeenCalledWith(
|
|
158
|
-
mockNamespace,
|
|
159
|
-
'MyProject-ABC123'
|
|
160
|
-
);
|
|
161
|
-
});
|
|
162
|
-
|
|
163
|
-
it('should normalize sandbox ID to lowercase when normalizeId option is true', () => {
|
|
164
|
-
const mockNamespace = {} as any;
|
|
165
|
-
getSandbox(mockNamespace, 'MyProject-ABC123', { normalizeId: true });
|
|
166
|
-
|
|
167
|
-
expect(mockGetContainer).toHaveBeenCalledWith(
|
|
168
|
-
mockNamespace,
|
|
169
|
-
'myproject-abc123'
|
|
170
|
-
);
|
|
171
|
-
});
|
|
172
|
-
});
|