Skip to content

Commit faa1683

Browse files
committed
feat: add e2e tests for dev server lifecycle
1 parent dbb9c00 commit faa1683

3 files changed

Lines changed: 135 additions & 2 deletions

File tree

e2e-tests/dev-lifecycle.test.ts

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
import { waitForServerReady } from '../src/cli/operations/dev/utils.js';
2+
import { cleanSpawnEnv, parseJsonOutput, spawnAndCollect } from '../src/test-utils/index.js';
3+
import { baseCanRun, runAgentCoreCLI } from './e2e-helper.js';
4+
import { type ChildProcess, spawn } from 'node:child_process';
5+
import { randomUUID } from 'node:crypto';
6+
import { mkdir, rm } from 'node:fs/promises';
7+
import { tmpdir } from 'node:os';
8+
import { join } from 'node:path';
9+
import { afterAll, beforeAll, describe, expect, it } from 'vitest';
10+
11+
const canRun = baseCanRun;
12+
const DEV_SERVER_PORT = 18080;
13+
14+
describe.sequential('e2e: dev server lifecycle', () => {
15+
let testDir: string;
16+
let projectPath: string;
17+
let serverProcess: ChildProcess | null = null;
18+
19+
beforeAll(async () => {
20+
if (!canRun) return;
21+
22+
testDir = join(tmpdir(), `agentcore-e2e-dev-${randomUUID()}`);
23+
await mkdir(testDir, { recursive: true });
24+
25+
const result = await runAgentCoreCLI(
26+
[
27+
'create',
28+
'--name',
29+
'DevTest',
30+
'--language',
31+
'Python',
32+
'--framework',
33+
'Strands',
34+
'--model-provider',
35+
'Bedrock',
36+
'--memory',
37+
'none',
38+
'--json',
39+
],
40+
testDir
41+
);
42+
expect(result.exitCode, `Create failed: ${result.stderr}`).toBe(0);
43+
projectPath = (parseJsonOutput(result.stdout) as { projectPath: string }).projectPath;
44+
}, 120000);
45+
46+
afterAll(async () => {
47+
if (serverProcess?.pid) {
48+
try {
49+
process.kill(-serverProcess.pid, 'SIGTERM');
50+
} catch {
51+
serverProcess.kill('SIGTERM');
52+
}
53+
await new Promise<void>(resolve => {
54+
serverProcess?.on('exit', () => resolve());
55+
setTimeout(resolve, 5000);
56+
});
57+
serverProcess = null;
58+
}
59+
if (testDir) {
60+
await rm(testDir, { recursive: true, force: true, maxRetries: 3, retryDelay: 1000 });
61+
}
62+
}, 30000);
63+
64+
it.skipIf(!canRun)(
65+
'dev --logs starts the server and accepts connections',
66+
async () => {
67+
serverProcess = spawn('agentcore', ['dev', '--logs', '--port', String(DEV_SERVER_PORT)], {
68+
cwd: projectPath,
69+
stdio: 'pipe',
70+
detached: true,
71+
env: cleanSpawnEnv(),
72+
});
73+
74+
const ready = await waitForServerReady(DEV_SERVER_PORT, 90000);
75+
expect(ready, 'Dev server should accept connections').toBe(true);
76+
},
77+
120000
78+
);
79+
80+
it.skipIf(!canRun)(
81+
'dev invokes the running server and returns a response',
82+
async () => {
83+
const result = await spawnAndCollect(
84+
'agentcore',
85+
['dev', 'What is 2 plus 2? Reply with just the number.', '--port', String(DEV_SERVER_PORT)],
86+
projectPath
87+
);
88+
89+
expect(result.exitCode, `Invoke failed (exit ${result.exitCode}): ${result.stderr}`).toBe(0);
90+
expect(result.stdout.length, 'Should produce a response').toBeGreaterThan(0);
91+
},
92+
60000
93+
);
94+
95+
it.skipIf(!canRun)(
96+
'dev --stream returns a response',
97+
async () => {
98+
const result = await spawnAndCollect(
99+
'agentcore',
100+
['dev', 'Say hello', '--stream', '--port', String(DEV_SERVER_PORT)],
101+
projectPath
102+
);
103+
104+
expect(result.exitCode, `Stream invoke failed (exit ${result.exitCode}): ${result.stderr}`).toBe(0);
105+
expect(result.stdout.length, 'Should produce output').toBeGreaterThan(0);
106+
},
107+
60000
108+
);
109+
110+
it.skipIf(!canRun)(
111+
'dev server handles sequential requests',
112+
async () => {
113+
const result = await spawnAndCollect(
114+
'agentcore',
115+
['dev', 'What is 3 plus 5? Reply with just the number.', '--port', String(DEV_SERVER_PORT)],
116+
projectPath
117+
);
118+
119+
expect(result.exitCode, `Second invoke failed (exit ${result.exitCode}): ${result.stderr}`).toBe(0);
120+
expect(result.stdout.length, 'Should produce a response').toBeGreaterThan(0);
121+
},
122+
60000
123+
);
124+
});

src/test-utils/cli-runner.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,15 @@ export interface RunResult {
1313
exitCode: number;
1414
}
1515

16+
/**
17+
* Build a clean env for spawned CLI processes.
18+
* Strips INIT_CWD which npm/npx sets to the runner's directory — without this,
19+
* the CLI resolves the working directory from INIT_CWD instead of the spawn's cwd.
20+
*/
21+
export function cleanSpawnEnv(extraEnv: Record<string, string> = {}): NodeJS.ProcessEnv {
22+
return { ...process.env, INIT_CWD: undefined, ...extraEnv };
23+
}
24+
1625
/**
1726
* Spawn a command, collect output, and strip ANSI codes.
1827
*/
@@ -25,7 +34,7 @@ export function spawnAndCollect(
2534
return new Promise(resolve => {
2635
const proc = spawn(command, args, {
2736
cwd,
28-
env: { ...process.env, INIT_CWD: undefined, ...extraEnv },
37+
env: cleanSpawnEnv(extraEnv),
2938
});
3039

3140
let stdout = '';

src/test-utils/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
* Import these helpers instead of duplicating code in each test file.
44
*/
55

6-
export { runCLI, spawnAndCollect, type RunResult } from './cli-runner.js';
6+
export { runCLI, spawnAndCollect, cleanSpawnEnv, type RunResult } from './cli-runner.js';
77
export { exists } from './fs-helpers.js';
88
export { hasCommand, hasAwsCredentials, prereqs } from './prereqs.js';
99
export { createTestProject, type TestProject, type CreateTestProjectOptions } from './project-factory.js';

0 commit comments

Comments
 (0)