Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
110 changes: 110 additions & 0 deletions e2e-tests/dev-lifecycle.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
import { waitForServerReady } from '../src/cli/operations/dev/utils.js';
import { cleanSpawnEnv, parseJsonOutput, spawnAndCollect } from '../src/test-utils/index.js';
import { baseCanRun as canRun, runAgentCoreCLI } from './e2e-helper.js';
import { type ChildProcess, spawn } from 'node:child_process';
import { randomUUID } from 'node:crypto';
import { mkdir, rm } from 'node:fs/promises';
import { tmpdir } from 'node:os';
import { join } from 'node:path';
import { afterAll, beforeAll, describe, expect, it } from 'vitest';
import { z } from 'zod';

const DEV_SERVER_PORT = 18080;
const DEV_SERVER_PORT_STR = String(DEV_SERVER_PORT);

describe.sequential('e2e: dev server lifecycle', () => {
let testDir: string;
let projectPath: string;
let serverProcess: ChildProcess | null = null;

beforeAll(async () => {
if (!canRun) return;

testDir = join(tmpdir(), `agentcore-e2e-dev-${randomUUID()}`);
await mkdir(testDir, { recursive: true });

const result = await runAgentCoreCLI(
[
'create',
'--name',
'DevTest',
'--language',
'Python',
'--framework',
'Strands',
'--model-provider',
'Bedrock',
'--memory',
'none',
'--json',
],
testDir
);
expect(result.exitCode, `Create failed: ${result.stderr}`).toBe(0);
projectPath = z.object({ projectPath: z.string() }).parse(parseJsonOutput(result.stdout)).projectPath;
}, 120000);

afterAll(async () => {
if (serverProcess?.pid) {
try {
process.kill(-serverProcess.pid, 'SIGTERM');
} catch {
// Process group already exited
}
await new Promise<void>(resolve => {
serverProcess?.on('exit', () => resolve());
setTimeout(resolve, 5000);
});
serverProcess = null;
}
if (testDir) {
await rm(testDir, { recursive: true, force: true, maxRetries: 3, retryDelay: 1000 });
}
}, 30000);

it.skipIf(!canRun)(
'dev --logs starts the server and accepts connections',
async () => {
serverProcess = spawn('agentcore', ['dev', '--logs', '--port', DEV_SERVER_PORT_STR], {
cwd: projectPath,
stdio: 'pipe',
detached: true,
env: cleanSpawnEnv(),
});

const ready = await waitForServerReady(DEV_SERVER_PORT, 90000);
expect(ready, 'Dev server should accept connections').toBe(true);
},
120000
);

it.skipIf(!canRun)(
'dev invokes the running server and returns a response',
async () => {
const result = await spawnAndCollect(
'agentcore',
['dev', 'What is 2 plus 2? Reply with just the number.', '--port', DEV_SERVER_PORT_STR],
projectPath
);

expect(result.exitCode, `Invoke failed (exit ${result.exitCode}): ${result.stderr}`).toBe(0);
expect(result.stdout.length, 'Should produce a response').toBeGreaterThan(0);
},
60000
);

it.skipIf(!canRun)(
'dev --stream returns a response',
async () => {
const result = await spawnAndCollect(
'agentcore',
['dev', 'Say hello', '--stream', '--port', DEV_SERVER_PORT_STR],
projectPath
);

expect(result.exitCode, `Stream invoke failed (exit ${result.exitCode}): ${result.stderr}`).toBe(0);
expect(result.stdout.length, 'Should produce output').toBeGreaterThan(0);
},
60000
);
});
12 changes: 11 additions & 1 deletion src/test-utils/cli-runner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,16 @@ export interface RunResult {
exitCode: number;
}

/**
* Build a clean env for spawned CLI processes.
* Strips INIT_CWD which npm/npx sets to the runner's directory — without this,
* the CLI resolves the working directory from INIT_CWD instead of the spawn's cwd.
* @see https://docs.npmjs.com/cli/v10/commands/npm-run-script
*/
export function cleanSpawnEnv(extraEnv: Record<string, string> = {}): NodeJS.ProcessEnv {
return { ...process.env, INIT_CWD: undefined, ...extraEnv };
}

/**
* Spawn a command, collect output, and strip ANSI codes.
*/
Expand All @@ -25,7 +35,7 @@ export function spawnAndCollect(
return new Promise(resolve => {
const proc = spawn(command, args, {
cwd,
env: { ...process.env, INIT_CWD: undefined, ...extraEnv },
env: cleanSpawnEnv(extraEnv),
});

let stdout = '';
Expand Down
2 changes: 1 addition & 1 deletion src/test-utils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
* Import these helpers instead of duplicating code in each test file.
*/

export { runCLI, spawnAndCollect, type RunResult } from './cli-runner.js';
export { runCLI, spawnAndCollect, cleanSpawnEnv, type RunResult } from './cli-runner.js';
export { exists } from './fs-helpers.js';
export { hasCommand, hasAwsCredentials, prereqs } from './prereqs.js';
export { createTestProject, type TestProject, type CreateTestProjectOptions } from './project-factory.js';
Expand Down
Loading