Skip to content

Commit 2a38dd2

Browse files
authored
Merge pull request #226 from codesandbox/add-full-e2e-tests
chore: add e2e tests for sdk
2 parents 77521e0 + 0bf33b6 commit 2a38dd2

12 files changed

+1462
-0
lines changed

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@
5555
"build-openapi-pint": "node_modules/.bin/openapi-ts -i ./pint-openapi-bundled.json -o src/api-clients/pint -c @hey-api/client-fetch",
5656
"clean": "rimraf ./dist",
5757
"test": "vitest",
58+
"test:e2e": "vitest run tests/e2e",
5859
"typecheck": "tsc --noEmit",
5960
"format": "prettier '**/*.{md,js,jsx,json,ts,tsx}' --write",
6061
"postbuild": "rimraf {lib,es}/**/__tests__ {lib,es}/**/*.{spec,test}.{js,d.ts,js.map}",

tests/e2e/helpers.ts

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import { CodeSandbox } from '../../src/index.js';
2+
3+
/**
4+
* Test template ID used across e2e tests
5+
*/
6+
export const TEST_TEMPLATE_ID = process.env.CSB_TEST_TEMPLATE_ID ?? 'pt_6FknW3VwS874LKWUQ83jir';
7+
8+
/**
9+
* Initialize SDK with API key from environment
10+
*/
11+
export function initializeSDK(): CodeSandbox {
12+
const apiKey = process.env.CSB_API_KEY;
13+
if (!apiKey) {
14+
throw new Error('CSB_API_KEY environment variable is required for e2e tests');
15+
}
16+
return new CodeSandbox(apiKey);
17+
}
18+
19+
/**
20+
* Retry a check function until it returns truthy or timeout is reached
21+
*/
22+
export async function retryUntil<T>(
23+
timeoutMs: number,
24+
intervalMs: number,
25+
checkFunction: () => Promise<T | null | undefined | false>
26+
): Promise<T | null> {
27+
const startTime = Date.now();
28+
let result: T | null | undefined | false = false;
29+
30+
while (Date.now() - startTime < timeoutMs && !result) {
31+
result = await checkFunction();
32+
if (!result) {
33+
await new Promise((resolve) => setTimeout(resolve, intervalMs));
34+
}
35+
}
36+
37+
return result || null;
38+
}

tests/e2e/sandbox-apis.test.ts

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
import { describe, it, expect, beforeAll, afterAll } from 'vitest';
2+
import { CodeSandbox } from '../../src/index.js';
3+
import { initializeSDK, TEST_TEMPLATE_ID, retryUntil } from './helpers.js';
4+
5+
describe('Sandbox APIs', () => {
6+
let sdk: CodeSandbox;
7+
let sandboxId: string | undefined;
8+
9+
beforeAll(async () => {
10+
sdk = initializeSDK();
11+
12+
// Create a sandbox for testing
13+
const sandbox = await sdk.sandboxes.create({
14+
id: TEST_TEMPLATE_ID,
15+
});
16+
sandboxId = sandbox.id;
17+
});
18+
19+
afterAll(async () => {
20+
// Cleanup: shutdown and delete the sandbox
21+
if (sandboxId) {
22+
try {
23+
await sdk.sandboxes.shutdown(sandboxId);
24+
await sdk.sandboxes.delete(sandboxId);
25+
} catch (error) {
26+
console.error('Failed to cleanup test sandbox:', sandboxId, error);
27+
// Try to force delete even if shutdown fails
28+
try {
29+
await sdk.sandboxes.delete(sandboxId);
30+
} catch (deleteError) {
31+
console.error('Failed to force delete sandbox:', sandboxId, deleteError);
32+
}
33+
}
34+
}
35+
});
36+
37+
it('should find sandbox in list', async () => {
38+
expect(sandboxId).toBeDefined();
39+
if (!sandboxId) throw new Error('Sandbox not created');
40+
41+
const sandboxes = await sdk.sandboxes.list();
42+
expect(sandboxes).toBeDefined();
43+
expect(sandboxes.sandboxes).toBeDefined();
44+
45+
const found = sandboxes.sandboxes.find((s) => s.id === sandboxId);
46+
expect(found).toBeDefined();
47+
});
48+
49+
it('should find sandbox in running list by filter', async () => {
50+
expect(sandboxId).toBeDefined();
51+
if (!sandboxId) throw new Error('Sandbox not created');
52+
53+
const foundInList = await retryUntil(60000, 3000, async () => {
54+
const runningSandboxesByFilter = await sdk.sandboxes.list({
55+
status: 'running',
56+
});
57+
return runningSandboxesByFilter.sandboxes.find((s) => s.id === sandboxId);
58+
});
59+
60+
expect(foundInList).toBeDefined();
61+
}, 70000);
62+
63+
it('should find sandbox in running list by API', async () => {
64+
expect(sandboxId).toBeDefined();
65+
if (!sandboxId) throw new Error('Sandbox not created');
66+
67+
const foundByAPI = await retryUntil(60000, 3000, async () => {
68+
const runningSandboxByAPI = await sdk.sandboxes.listRunning();
69+
return runningSandboxByAPI.vms.find((s) => s.id === sandboxId);
70+
});
71+
72+
expect(foundByAPI).toBeDefined();
73+
}, 70000);
74+
75+
it('should get sandbox by ID', async () => {
76+
expect(sandboxId).toBeDefined();
77+
if (!sandboxId) throw new Error('Sandbox not created');
78+
79+
const fetchedSandbox = await sdk.sandboxes.get(sandboxId);
80+
expect(fetchedSandbox).toBeDefined();
81+
expect(fetchedSandbox.id).toBe(sandboxId);
82+
});
83+
});

tests/e2e/sandbox-commands.test.ts

Lines changed: 175 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,175 @@
1+
import { describe, it, expect, beforeAll, afterAll } from 'vitest';
2+
import { CodeSandbox } from '../../src/index.js';
3+
import { Sandbox } from '../../src/Sandbox.js';
4+
import { SandboxClient } from '../../src/SandboxClient/index.js';
5+
import { initializeSDK, TEST_TEMPLATE_ID } from './helpers.js';
6+
7+
describe('Sandbox Commands', () => {
8+
let sdk: CodeSandbox;
9+
let sandbox: Sandbox | undefined;
10+
let client: SandboxClient | undefined;
11+
12+
beforeAll(async () => {
13+
sdk = initializeSDK();
14+
15+
// Create a sandbox for testing
16+
sandbox = await sdk.sandboxes.create({
17+
id: TEST_TEMPLATE_ID,
18+
});
19+
20+
// Connect to sandbox
21+
client = await sandbox.connect();
22+
}, 60000);
23+
24+
afterAll(async () => {
25+
const sandboxId = sandbox?.id;
26+
27+
try {
28+
if (client) {
29+
await client.disconnect();
30+
client.dispose();
31+
client = undefined;
32+
}
33+
} catch (error) {
34+
console.error('Failed to dispose client:', error);
35+
}
36+
37+
if (sandboxId) {
38+
try {
39+
await sdk.sandboxes.shutdown(sandboxId);
40+
await sdk.sandboxes.delete(sandboxId);
41+
} catch (error) {
42+
console.error('Failed to cleanup test sandbox:', sandboxId, error);
43+
try {
44+
await sdk.sandboxes.delete(sandboxId);
45+
} catch (deleteError) {
46+
console.error('Failed to force delete sandbox:', sandboxId, deleteError);
47+
}
48+
}
49+
}
50+
});
51+
52+
describe('Command execution', () => {
53+
it('should run a simple command and get output', async () => {
54+
if (!client || !sandbox) throw new Error('Client or sandbox not initialized');
55+
56+
const output = await client.commands.run('echo "Hello from sandbox"');
57+
expect(output).toContain('Hello from sandbox');
58+
});
59+
60+
it('should get output from pwd command', async () => {
61+
if (!client || !sandbox) throw new Error('Client or sandbox not initialized');
62+
63+
const output = await client.commands.run('pwd');
64+
expect(output).toBeTruthy();
65+
expect(output.trim()).toMatch(/^\//); // Should start with /
66+
});
67+
68+
it('should run multiple commands sequentially', async () => {
69+
if (!client || !sandbox) throw new Error('Client or sandbox not initialized');
70+
71+
const output1 = await client.commands.run('echo "first"');
72+
const output2 = await client.commands.run('echo "second"');
73+
const output3 = await client.commands.run('echo "third"');
74+
75+
expect(output1).toContain('first');
76+
expect(output2).toContain('second');
77+
expect(output3).toContain('third');
78+
});
79+
80+
it('should run multiple commands with array syntax', async () => {
81+
if (!client || !sandbox) throw new Error('Client or sandbox not initialized');
82+
83+
// Array of commands should be joined with &&
84+
const output = await client.commands.run([
85+
'echo "first"',
86+
'echo "second"',
87+
'echo "third"',
88+
]);
89+
90+
expect(output).toContain('first');
91+
expect(output).toContain('second');
92+
expect(output).toContain('third');
93+
});
94+
});
95+
96+
describe('Background commands', () => {
97+
it('should run command in background', async () => {
98+
if (!client || !sandbox) throw new Error('Client or sandbox not initialized');
99+
100+
const command = await client.commands.runBackground('sleep 1 && echo "done"');
101+
expect(command).toBeDefined();
102+
expect(command.status).toBe('RUNNING');
103+
104+
// Wait for completion
105+
const output = await command.waitUntilComplete();
106+
expect(output).toContain('done');
107+
}, 10000);
108+
109+
it('should run multiple commands in background with array syntax', async () => {
110+
if (!client || !sandbox) throw new Error('Client or sandbox not initialized');
111+
112+
// Array of commands should be joined with &&
113+
const command = await client.commands.runBackground([
114+
'echo "first"',
115+
'echo "second"',
116+
'echo "third"',
117+
]);
118+
expect(command).toBeDefined();
119+
expect(command.status).toBe('RUNNING');
120+
121+
// Wait for completion
122+
const output = await command.waitUntilComplete();
123+
expect(output).toContain('first');
124+
expect(output).toContain('second');
125+
expect(output).toContain('third');
126+
}, 10000);
127+
128+
it('should be able to kill background command', async () => {
129+
if (!client || !sandbox) throw new Error('Client or sandbox not initialized');
130+
131+
const command = await client.commands.runBackground('sleep 30');
132+
expect(command).toBeDefined();
133+
134+
await command.kill();
135+
136+
// Command should be killed
137+
expect(command).toBeDefined();
138+
}, 10000);
139+
});
140+
141+
describe('Command listing', () => {
142+
it('should get all commands', async () => {
143+
if (!client || !sandbox) throw new Error('Client or sandbox not initialized');
144+
145+
const commands = await client.commands.getAll();
146+
expect(Array.isArray(commands)).toBe(true);
147+
});
148+
});
149+
150+
describe('Working directory', () => {
151+
it('should run command in specified directory', async () => {
152+
if (!client || !sandbox) throw new Error('Client or sandbox not initialized');
153+
154+
// Create a test directory
155+
await client.fs.mkdir('/test-cwd');
156+
157+
const output = await client.commands.run('pwd', { cwd: '/test-cwd' });
158+
expect(output).toContain('/test-cwd');
159+
160+
// Cleanup
161+
await client.fs.remove('/test-cwd');
162+
});
163+
});
164+
165+
describe('Environment variables', () => {
166+
it('should run command with custom environment variables', async () => {
167+
if (!client || !sandbox) throw new Error('Client or sandbox not initialized');
168+
169+
const output = await client.commands.run('echo $TEST_VAR', {
170+
env: { TEST_VAR: 'custom_value' },
171+
});
172+
expect(output).toContain('custom_value');
173+
});
174+
});
175+
});

0 commit comments

Comments
 (0)