Skip to content

Commit f3269bf

Browse files
complete env override test and poll for process termination
1 parent dc702b1 commit f3269bf

File tree

2 files changed

+190
-16
lines changed

2 files changed

+190
-16
lines changed

tests/e2e/comprehensive-workflow.test.ts

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -609,15 +609,18 @@ const interval = setInterval(() => {
609609

610610
expect(killAllResponse.status).toBe(200);
611611

612-
// Verify no running processes remain
613-
await new Promise((resolve) => setTimeout(resolve, 500));
614-
const listAfterResponse = await fetch(`${workerUrl}/api/process/list`, {
615-
method: 'GET',
616-
headers
617-
});
618-
619-
const processesAfter = (await listAfterResponse.json()) as Process[];
620-
const running = processesAfter.filter((p) => p.status === 'running');
612+
// Poll until no running processes remain (up to 5 seconds)
613+
let running: Process[] = [];
614+
for (let i = 0; i < 10; i++) {
615+
await new Promise((resolve) => setTimeout(resolve, 500));
616+
const listAfterResponse = await fetch(`${workerUrl}/api/process/list`, {
617+
method: 'GET',
618+
headers
619+
});
620+
const processesAfter = (await listAfterResponse.json()) as Process[];
621+
running = processesAfter.filter((p) => p.status === 'running');
622+
if (running.length === 0) break;
623+
}
621624
expect(running.length).toBe(0);
622625
}, 60000);
623626
});

tests/e2e/environment-workflow.test.ts

Lines changed: 178 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,18 +3,24 @@ import {
33
getSharedSandbox,
44
createUniqueSession
55
} from './helpers/global-sandbox';
6-
import type { ExecResult } from '@repo/shared';
6+
import type { ExecResult, ExecEvent } from '@repo/shared';
7+
import { parseSSEStream } from '../../packages/sandbox/src/sse-parser';
78

89
/**
9-
* Environment Edge Case Tests
10+
* Environment Variable Tests
1011
*
11-
* Tests edge cases for environment and command execution.
12-
* Happy path tests (env vars, persistence, per-command env/cwd) are in comprehensive-workflow.test.ts.
12+
* Tests all ways to set environment variables and their override behavior:
13+
* - Dockerfile ENV (base level, e.g. SANDBOX_VERSION)
14+
* - setEnvVars at session level
15+
* - Per-command env in exec()
16+
* - Per-command env in execStream()
1317
*
14-
* This file focuses on:
15-
* - Commands that read stdin (should not hang)
18+
* Override precedence (highest to lowest):
19+
* 1. Per-command env
20+
* 2. Session-level setEnvVars
21+
* 3. Dockerfile ENV
1622
*/
17-
describe('Environment Edge Cases', () => {
23+
describe('Environment Variables', () => {
1824
let workerUrl: string;
1925
let headers: Record<string, string>;
2026

@@ -24,6 +30,171 @@ describe('Environment Edge Cases', () => {
2430
headers = sandbox.createHeaders(createUniqueSession());
2531
}, 120000);
2632

33+
test('should have Dockerfile ENV vars available', async () => {
34+
// SANDBOX_VERSION is set in the Dockerfile
35+
const response = await fetch(`${workerUrl}/api/execute`, {
36+
method: 'POST',
37+
headers,
38+
body: JSON.stringify({ command: 'echo $SANDBOX_VERSION' })
39+
});
40+
41+
expect(response.status).toBe(200);
42+
const data = (await response.json()) as ExecResult;
43+
expect(data.success).toBe(true);
44+
// Should have some version value (not empty)
45+
expect(data.stdout.trim()).toBeTruthy();
46+
expect(data.stdout.trim()).not.toBe('$SANDBOX_VERSION');
47+
}, 30000);
48+
49+
test('should set and persist session-level env vars via setEnvVars', async () => {
50+
// Set env vars at session level
51+
const setResponse = await fetch(`${workerUrl}/api/env/set`, {
52+
method: 'POST',
53+
headers,
54+
body: JSON.stringify({
55+
envVars: {
56+
MY_SESSION_VAR: 'session-value',
57+
ANOTHER_VAR: 'another-value'
58+
}
59+
})
60+
});
61+
62+
expect(setResponse.status).toBe(200);
63+
64+
// Verify they persist across commands
65+
const readResponse = await fetch(`${workerUrl}/api/execute`, {
66+
method: 'POST',
67+
headers,
68+
body: JSON.stringify({
69+
command: 'echo "$MY_SESSION_VAR:$ANOTHER_VAR"'
70+
})
71+
});
72+
73+
expect(readResponse.status).toBe(200);
74+
const readData = (await readResponse.json()) as ExecResult;
75+
expect(readData.stdout.trim()).toBe('session-value:another-value');
76+
}, 30000);
77+
78+
test('should support per-command env in exec()', async () => {
79+
const response = await fetch(`${workerUrl}/api/execute`, {
80+
method: 'POST',
81+
headers,
82+
body: JSON.stringify({
83+
command: 'echo "$CMD_VAR"',
84+
env: { CMD_VAR: 'command-specific-value' }
85+
})
86+
});
87+
88+
expect(response.status).toBe(200);
89+
const data = (await response.json()) as ExecResult;
90+
expect(data.stdout.trim()).toBe('command-specific-value');
91+
}, 30000);
92+
93+
test('should support per-command env in execStream()', async () => {
94+
const response = await fetch(`${workerUrl}/api/execStream`, {
95+
method: 'POST',
96+
headers,
97+
body: JSON.stringify({
98+
command: 'echo "$STREAM_VAR"',
99+
env: { STREAM_VAR: 'stream-env-value' }
100+
})
101+
});
102+
103+
expect(response.status).toBe(200);
104+
105+
// Collect streamed output
106+
const events: ExecEvent[] = [];
107+
const abortController = new AbortController();
108+
for await (const event of parseSSEStream<ExecEvent>(
109+
response.body!,
110+
abortController.signal
111+
)) {
112+
events.push(event);
113+
if (event.type === 'complete' || event.type === 'error') break;
114+
}
115+
116+
const stdout = events
117+
.filter((e) => e.type === 'stdout')
118+
.map((e) => e.data)
119+
.join('');
120+
expect(stdout.trim()).toBe('stream-env-value');
121+
}, 30000);
122+
123+
test('should override session env with per-command env', async () => {
124+
// First set a session-level var
125+
await fetch(`${workerUrl}/api/env/set`, {
126+
method: 'POST',
127+
headers,
128+
body: JSON.stringify({
129+
envVars: { OVERRIDE_TEST: 'session-level' }
130+
})
131+
});
132+
133+
// Verify session value
134+
const sessionResponse = await fetch(`${workerUrl}/api/execute`, {
135+
method: 'POST',
136+
headers,
137+
body: JSON.stringify({ command: 'echo "$OVERRIDE_TEST"' })
138+
});
139+
const sessionData = (await sessionResponse.json()) as ExecResult;
140+
expect(sessionData.stdout.trim()).toBe('session-level');
141+
142+
// Override with per-command env
143+
const overrideResponse = await fetch(`${workerUrl}/api/execute`, {
144+
method: 'POST',
145+
headers,
146+
body: JSON.stringify({
147+
command: 'echo "$OVERRIDE_TEST"',
148+
env: { OVERRIDE_TEST: 'command-level' }
149+
})
150+
});
151+
const overrideData = (await overrideResponse.json()) as ExecResult;
152+
expect(overrideData.stdout.trim()).toBe('command-level');
153+
154+
// Session value should still be intact
155+
const afterResponse = await fetch(`${workerUrl}/api/execute`, {
156+
method: 'POST',
157+
headers,
158+
body: JSON.stringify({ command: 'echo "$OVERRIDE_TEST"' })
159+
});
160+
const afterData = (await afterResponse.json()) as ExecResult;
161+
expect(afterData.stdout.trim()).toBe('session-level');
162+
}, 30000);
163+
164+
test('should override Dockerfile ENV with session setEnvVars', async () => {
165+
// Create a fresh session to test clean override
166+
const sandbox = await getSharedSandbox();
167+
const freshHeaders = sandbox.createHeaders(createUniqueSession());
168+
169+
// First read Dockerfile value
170+
const beforeResponse = await fetch(`${workerUrl}/api/execute`, {
171+
method: 'POST',
172+
headers: freshHeaders,
173+
body: JSON.stringify({ command: 'echo "$SANDBOX_VERSION"' })
174+
});
175+
const beforeData = (await beforeResponse.json()) as ExecResult;
176+
const dockerValue = beforeData.stdout.trim();
177+
expect(dockerValue).toBeTruthy();
178+
179+
// Override with session setEnvVars
180+
await fetch(`${workerUrl}/api/env/set`, {
181+
method: 'POST',
182+
headers: freshHeaders,
183+
body: JSON.stringify({
184+
envVars: { SANDBOX_VERSION: 'overridden-version' }
185+
})
186+
});
187+
188+
// Verify override
189+
const afterResponse = await fetch(`${workerUrl}/api/execute`, {
190+
method: 'POST',
191+
headers: freshHeaders,
192+
body: JSON.stringify({ command: 'echo "$SANDBOX_VERSION"' })
193+
});
194+
const afterData = (await afterResponse.json()) as ExecResult;
195+
expect(afterData.stdout.trim()).toBe('overridden-version');
196+
}, 30000);
197+
27198
test('should handle commands that read stdin without hanging', async () => {
28199
// Test 1: cat with no arguments should exit immediately with EOF
29200
const catResponse = await fetch(`${workerUrl}/api/execute`, {

0 commit comments

Comments
 (0)