From 99fe3ce2249958dfcc0f9c7344e214e09bde1b07 Mon Sep 17 00:00:00 2001 From: Jesse Turner Date: Thu, 19 Feb 2026 11:42:10 -0500 Subject: [PATCH 1/2] all framework and models --- .github/workflows/e2e-tests.yml | 9 ++ e2e-tests/create-deploy-invoke.test.ts | 112 ------------------ e2e-tests/e2e-helper.ts | 152 +++++++++++++++++++++++++ e2e-tests/googleadk-gemini.test.ts | 3 + e2e-tests/langgraph-anthropic.test.ts | 3 + e2e-tests/langgraph-bedrock.test.ts | 3 + e2e-tests/langgraph-gemini.test.ts | 3 + e2e-tests/langgraph-openai.test.ts | 3 + e2e-tests/openaiagents-openai.test.ts | 3 + e2e-tests/strands-anthropic.test.ts | 3 + e2e-tests/strands-bedrock.test.ts | 3 + e2e-tests/strands-gemini.test.ts | 3 + e2e-tests/strands-openai.test.ts | 3 + package-lock.json | 128 +++++++++++++-------- 14 files changed, 271 insertions(+), 160 deletions(-) delete mode 100644 e2e-tests/create-deploy-invoke.test.ts create mode 100644 e2e-tests/e2e-helper.ts create mode 100644 e2e-tests/googleadk-gemini.test.ts create mode 100644 e2e-tests/langgraph-anthropic.test.ts create mode 100644 e2e-tests/langgraph-bedrock.test.ts create mode 100644 e2e-tests/langgraph-gemini.test.ts create mode 100644 e2e-tests/langgraph-openai.test.ts create mode 100644 e2e-tests/openaiagents-openai.test.ts create mode 100644 e2e-tests/strands-anthropic.test.ts create mode 100644 e2e-tests/strands-bedrock.test.ts create mode 100644 e2e-tests/strands-gemini.test.ts create mode 100644 e2e-tests/strands-openai.test.ts diff --git a/.github/workflows/e2e-tests.yml b/.github/workflows/e2e-tests.yml index 3bd6885f..344391d0 100644 --- a/.github/workflows/e2e-tests.yml +++ b/.github/workflows/e2e-tests.yml @@ -38,10 +38,19 @@ jobs: - name: Get AWS Account ID id: aws run: echo "account_id=$(aws sts get-caller-identity --query Account --output text)" >> "$GITHUB_OUTPUT" + - name: Get API keys from Secrets Manager + uses: aws-actions/aws-secretsmanager-get-secrets@v2 + with: + secret-ids: | + E2E,${{ secrets.E2E_SECRET_ARN }} + parse-json-secrets: true - run: npm ci - run: npm run build - name: Run E2E tests env: AWS_ACCOUNT_ID: ${{ steps.aws.outputs.account_id }} AWS_REGION: ${{ inputs.aws_region || 'us-east-1' }} + ANTHROPIC_API_KEY: ${{ env.E2E_ANTHROPIC_API_KEY }} + OPENAI_API_KEY: ${{ env.E2E_OPENAI_API_KEY }} + GEMINI_API_KEY: ${{ env.E2E_GEMINI_API_KEY }} run: npm run test:e2e diff --git a/e2e-tests/create-deploy-invoke.test.ts b/e2e-tests/create-deploy-invoke.test.ts deleted file mode 100644 index b026d160..00000000 --- a/e2e-tests/create-deploy-invoke.test.ts +++ /dev/null @@ -1,112 +0,0 @@ -import { hasAwsCredentials, parseJsonOutput, prereqs, runCLI } from '../src/test-utils/index.js'; -import { execSync } from 'node:child_process'; -import { randomUUID } from 'node:crypto'; -import { mkdir, rm, writeFile } from 'node:fs/promises'; -import { tmpdir } from 'node:os'; -import { join } from 'node:path'; -import { afterAll, beforeAll, describe, expect, it } from 'vitest'; - -const hasAws = hasAwsCredentials(); -const canRun = prereqs.npm && prereqs.git && prereqs.uv && hasAws; - -describe.sequential('e2e: create → deploy → invoke', () => { - let testDir: string; - let projectPath: string; - let agentName: string; - - beforeAll(async () => { - if (!canRun) return; - - testDir = join(tmpdir(), `agentcore-e2e-${randomUUID()}`); - await mkdir(testDir, { recursive: true }); - - agentName = `E2e${Date.now()}`; - const result = await runCLI( - [ - 'create', - '--name', - agentName, - '--language', - 'Python', - '--framework', - 'Strands', - '--model-provider', - 'Bedrock', - '--memory', - 'none', - '--json', - ], - testDir, - false - ); - - expect(result.exitCode, `Create failed: ${result.stderr}`).toBe(0); - const json = parseJsonOutput(result.stdout) as { projectPath: string }; - projectPath = json.projectPath; - - // TODO: Replace with `agentcore add target` once the CLI command is re-introduced - const account = - process.env.AWS_ACCOUNT_ID || - execSync('aws sts get-caller-identity --query Account --output text').toString().trim(); - const region = process.env.AWS_REGION || 'us-east-1'; - const awsTargetsPath = join(projectPath, 'agentcore', 'aws-targets.json'); - await writeFile(awsTargetsPath, JSON.stringify([{ name: 'default', account, region }])); - }, 120000); - - afterAll(async () => { - if (projectPath && hasAws) { - await runCLI(['remove', 'all', '--json'], projectPath, false); - const result = await runCLI(['deploy', '--yes', '--json'], projectPath, false); - - if (result.exitCode !== 0) { - console.log('Teardown stdout:', result.stdout); - console.log('Teardown stderr:', result.stderr); - } - } - if (testDir) await rm(testDir, { recursive: true, force: true }); - }, 600000); - - it.skipIf(!canRun)( - 'deploys to AWS successfully', - async () => { - expect(projectPath, 'Project should have been created').toBeTruthy(); - - const result = await runCLI(['deploy', '--yes', '--json'], projectPath, false); - - if (result.exitCode !== 0) { - console.log('Deploy stdout:', result.stdout); - console.log('Deploy stderr:', result.stderr); - } - - expect(result.exitCode, `Deploy failed: ${result.stderr}`).toBe(0); - - const json = parseJsonOutput(result.stdout) as { success: boolean }; - expect(json.success, 'Deploy should report success').toBe(true); - }, - 300000 - ); - - it.skipIf(!canRun)( - 'invokes the deployed agent', - async () => { - expect(projectPath, 'Project should have been created').toBeTruthy(); - - const result = await runCLI( - ['invoke', '--prompt', 'Say hello', '--agent', agentName, '--json'], - projectPath, - false - ); - - if (result.exitCode !== 0) { - console.log('Invoke stdout:', result.stdout); - console.log('Invoke stderr:', result.stderr); - } - - expect(result.exitCode, `Invoke failed: ${result.stderr}`).toBe(0); - - const json = parseJsonOutput(result.stdout) as { success: boolean }; - expect(json.success, 'Invoke should report success').toBe(true); - }, - 120000 - ); -}); diff --git a/e2e-tests/e2e-helper.ts b/e2e-tests/e2e-helper.ts new file mode 100644 index 00000000..764f9d0f --- /dev/null +++ b/e2e-tests/e2e-helper.ts @@ -0,0 +1,152 @@ +import { hasAwsCredentials, parseJsonOutput, prereqs, runCLI } from '../src/test-utils/index.js'; +import { execSync } from 'node:child_process'; +import { randomUUID } from 'node:crypto'; +import { mkdir, rm, writeFile } from 'node:fs/promises'; +import { tmpdir } from 'node:os'; +import { join } from 'node:path'; +import { afterAll, beforeAll, describe, expect, it } from 'vitest'; + +const hasAws = hasAwsCredentials(); +const baseCanRun = prereqs.npm && prereqs.git && prereqs.uv && hasAws; + +interface E2EConfig { + framework: string; + modelProvider: string; + requiredEnvVar?: string; +} + +/** + * Retry an async function up to `times` attempts with a delay between retries. + */ +async function retry(fn: () => Promise, times: number, delayMs: number): Promise { + let lastError: unknown; + for (let i = 0; i < times; i++) { + try { + return await fn(); + } catch (err) { + lastError = err; + if (i < times - 1) { + await new Promise(resolve => setTimeout(resolve, delayMs)); + } + } + } + throw lastError; +} + +export function createE2ESuite(cfg: E2EConfig) { + const hasApiKey = !cfg.requiredEnvVar || !!process.env[cfg.requiredEnvVar]; + const canRun = baseCanRun && hasApiKey; + + describe.sequential(`e2e: ${cfg.framework}/${cfg.modelProvider} — create → deploy → invoke`, () => { + let testDir: string; + let projectPath: string; + let agentName: string; + + beforeAll(async () => { + if (!canRun) return; + + testDir = join(tmpdir(), `agentcore-e2e-${randomUUID()}`); + await mkdir(testDir, { recursive: true }); + + agentName = `E2e${cfg.framework.slice(0, 4)}${cfg.modelProvider.slice(0, 4)}${String(Date.now()).slice(-8)}`; + const createArgs = [ + 'create', + '--name', + agentName, + '--language', + 'Python', + '--framework', + cfg.framework, + '--model-provider', + cfg.modelProvider, + '--memory', + 'none', + '--json', + ]; + + // Pass API key so the credential is registered in the project and .env.local + const apiKey = cfg.requiredEnvVar ? process.env[cfg.requiredEnvVar] : undefined; + if (apiKey) { + createArgs.push('--api-key', apiKey); + } + + const result = await runCLI(createArgs, testDir, false); + + expect(result.exitCode, `Create failed: ${result.stderr}`).toBe(0); + const json = parseJsonOutput(result.stdout) as { projectPath: string }; + projectPath = json.projectPath; + + // TODO: Replace with `agentcore add target` once the CLI command is re-introduced + const account = + process.env.AWS_ACCOUNT_ID ?? + execSync('aws sts get-caller-identity --query Account --output text').toString().trim(); + const region = process.env.AWS_REGION ?? 'us-east-1'; + const awsTargetsPath = join(projectPath, 'agentcore', 'aws-targets.json'); + await writeFile(awsTargetsPath, JSON.stringify([{ name: 'default', account, region }])); + }, 300000); + + afterAll(async () => { + if (projectPath && hasAws) { + await runCLI(['remove', 'all', '--json'], projectPath, false); + const result = await runCLI(['deploy', '--yes', '--json'], projectPath, false); + + if (result.exitCode !== 0) { + console.log('Teardown stdout:', result.stdout); + console.log('Teardown stderr:', result.stderr); + } + } + if (testDir) await rm(testDir, { recursive: true, force: true, maxRetries: 3, retryDelay: 1000 }); + }, 600000); + + it.skipIf(!canRun)( + 'deploys to AWS successfully', + async () => { + expect(projectPath, 'Project should have been created').toBeTruthy(); + + const result = await runCLI(['deploy', '--yes', '--json'], projectPath, false); + + if (result.exitCode !== 0) { + console.log('Deploy stdout:', result.stdout); + console.log('Deploy stderr:', result.stderr); + } + + expect(result.exitCode, `Deploy failed: ${result.stderr}`).toBe(0); + + const json = parseJsonOutput(result.stdout) as { success: boolean }; + expect(json.success, 'Deploy should report success').toBe(true); + }, + 600000 + ); + + it.skipIf(!canRun)( + 'invokes the deployed agent', + async () => { + expect(projectPath, 'Project should have been created').toBeTruthy(); + + // Retry invoke to handle cold-start / runtime initialization delays + await retry( + async () => { + const result = await runCLI( + ['invoke', '--prompt', 'Say hello', '--agent', agentName, '--json'], + projectPath, + false + ); + + if (result.exitCode !== 0) { + console.log('Invoke stdout:', result.stdout); + console.log('Invoke stderr:', result.stderr); + } + + expect(result.exitCode, `Invoke failed: ${result.stderr}`).toBe(0); + + const json = parseJsonOutput(result.stdout) as { success: boolean }; + expect(json.success, 'Invoke should report success').toBe(true); + }, + 3, + 15000 + ); + }, + 180000 + ); + }); +} diff --git a/e2e-tests/googleadk-gemini.test.ts b/e2e-tests/googleadk-gemini.test.ts new file mode 100644 index 00000000..6c01a7b1 --- /dev/null +++ b/e2e-tests/googleadk-gemini.test.ts @@ -0,0 +1,3 @@ +import { createE2ESuite } from './e2e-helper.js'; + +createE2ESuite({ framework: 'GoogleADK', modelProvider: 'Gemini', requiredEnvVar: 'GEMINI_API_KEY' }); diff --git a/e2e-tests/langgraph-anthropic.test.ts b/e2e-tests/langgraph-anthropic.test.ts new file mode 100644 index 00000000..4bc4e562 --- /dev/null +++ b/e2e-tests/langgraph-anthropic.test.ts @@ -0,0 +1,3 @@ +import { createE2ESuite } from './e2e-helper.js'; + +createE2ESuite({ framework: 'LangChain_LangGraph', modelProvider: 'Anthropic', requiredEnvVar: 'ANTHROPIC_API_KEY' }); diff --git a/e2e-tests/langgraph-bedrock.test.ts b/e2e-tests/langgraph-bedrock.test.ts new file mode 100644 index 00000000..441c2d4f --- /dev/null +++ b/e2e-tests/langgraph-bedrock.test.ts @@ -0,0 +1,3 @@ +import { createE2ESuite } from './e2e-helper.js'; + +createE2ESuite({ framework: 'LangChain_LangGraph', modelProvider: 'Bedrock' }); diff --git a/e2e-tests/langgraph-gemini.test.ts b/e2e-tests/langgraph-gemini.test.ts new file mode 100644 index 00000000..3e9d247b --- /dev/null +++ b/e2e-tests/langgraph-gemini.test.ts @@ -0,0 +1,3 @@ +import { createE2ESuite } from './e2e-helper.js'; + +createE2ESuite({ framework: 'LangChain_LangGraph', modelProvider: 'Gemini', requiredEnvVar: 'GEMINI_API_KEY' }); diff --git a/e2e-tests/langgraph-openai.test.ts b/e2e-tests/langgraph-openai.test.ts new file mode 100644 index 00000000..9c4c0884 --- /dev/null +++ b/e2e-tests/langgraph-openai.test.ts @@ -0,0 +1,3 @@ +import { createE2ESuite } from './e2e-helper.js'; + +createE2ESuite({ framework: 'LangChain_LangGraph', modelProvider: 'OpenAI', requiredEnvVar: 'OPENAI_API_KEY' }); diff --git a/e2e-tests/openaiagents-openai.test.ts b/e2e-tests/openaiagents-openai.test.ts new file mode 100644 index 00000000..4d6195a1 --- /dev/null +++ b/e2e-tests/openaiagents-openai.test.ts @@ -0,0 +1,3 @@ +import { createE2ESuite } from './e2e-helper.js'; + +createE2ESuite({ framework: 'OpenAIAgents', modelProvider: 'OpenAI', requiredEnvVar: 'OPENAI_API_KEY' }); diff --git a/e2e-tests/strands-anthropic.test.ts b/e2e-tests/strands-anthropic.test.ts new file mode 100644 index 00000000..c0c7c8f3 --- /dev/null +++ b/e2e-tests/strands-anthropic.test.ts @@ -0,0 +1,3 @@ +import { createE2ESuite } from './e2e-helper.js'; + +createE2ESuite({ framework: 'Strands', modelProvider: 'Anthropic', requiredEnvVar: 'ANTHROPIC_API_KEY' }); diff --git a/e2e-tests/strands-bedrock.test.ts b/e2e-tests/strands-bedrock.test.ts new file mode 100644 index 00000000..2acbe2b0 --- /dev/null +++ b/e2e-tests/strands-bedrock.test.ts @@ -0,0 +1,3 @@ +import { createE2ESuite } from './e2e-helper.js'; + +createE2ESuite({ framework: 'Strands', modelProvider: 'Bedrock' }); diff --git a/e2e-tests/strands-gemini.test.ts b/e2e-tests/strands-gemini.test.ts new file mode 100644 index 00000000..4e2075f1 --- /dev/null +++ b/e2e-tests/strands-gemini.test.ts @@ -0,0 +1,3 @@ +import { createE2ESuite } from './e2e-helper.js'; + +createE2ESuite({ framework: 'Strands', modelProvider: 'Gemini', requiredEnvVar: 'GEMINI_API_KEY' }); diff --git a/e2e-tests/strands-openai.test.ts b/e2e-tests/strands-openai.test.ts new file mode 100644 index 00000000..d7261013 --- /dev/null +++ b/e2e-tests/strands-openai.test.ts @@ -0,0 +1,3 @@ +import { createE2ESuite } from './e2e-helper.js'; + +createE2ESuite({ framework: 'Strands', modelProvider: 'OpenAI', requiredEnvVar: 'OPENAI_API_KEY' }); diff --git a/package-lock.json b/package-lock.json index c9be97f7..2fa025e3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5333,13 +5333,13 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "25.2.3", - "resolved": "https://registry.npmjs.org/@types/node/-/node-25.2.3.tgz", - "integrity": "sha512-m0jEgYlYz+mDJZ2+F4v8D1AyQb+QzsNqRuI7xg1VQX/KlKS0qT9r1Mo16yo5F/MtifXFgaofIFsdFMox2SxIbQ==", + "version": "25.3.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.3.0.tgz", + "integrity": "sha512-4K3bqJpXpqfg2XKGK9bpDTc6xO/xoUP/RBWS7AtRMug6zZFaRekiLzjVtAoZMquxoAbzBvy5nxQ7veS5eYzf8A==", "dev": true, "license": "MIT", "dependencies": { - "undici-types": "~7.16.0" + "undici-types": "~7.18.0" } }, "node_modules/@types/normalize-package-data": { @@ -6005,9 +6005,9 @@ } }, "node_modules/acorn": { - "version": "8.15.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", - "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", + "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", "dev": true, "license": "MIT", "bin": { @@ -7045,13 +7045,16 @@ "license": "MIT" }, "node_modules/baseline-browser-mapping": { - "version": "2.9.19", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.19.tgz", - "integrity": "sha512-ipDqC8FrAl/76p2SSWKSI+H9tFwm7vYqXQrItCuiVPt26Km0jS+NzSsBWAaBusvSbQcfJG+JitdMm+wZAgTYqg==", + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.0.tgz", + "integrity": "sha512-lIyg0szRfYbiy67j9KN8IyeD7q7hcmqnJ1ddWmNt19ItGpNN64mnllmxUNFIOdOm6by97jlL6wfpTTJrmnjWAA==", "dev": true, "license": "Apache-2.0", "bin": { - "baseline-browser-mapping": "dist/cli.js" + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" } }, "node_modules/binaryextensions": { @@ -7341,6 +7344,34 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/cli-truncate/node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/cli-truncate/node_modules/slice-ansi": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-7.1.2.tgz", + "integrity": "sha512-iOBWFgUX7caIZiuutICxVgX1SdxwAVFFKwt1EvMYYec/NWO5meOJ6K5uQxhrYBdQJne4KxiqZc+KptFOWFSI9w==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.2.1", + "is-fullwidth-code-point": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/slice-ansi?sponsor=1" + } + }, "node_modules/cli-truncate/node_modules/string-width": { "version": "8.2.0", "resolved": "https://registry.npmjs.org/string-width/-/string-width-8.2.0.tgz", @@ -7427,9 +7458,9 @@ "license": "MIT" }, "node_modules/constructs": { - "version": "10.5.0", - "resolved": "https://registry.npmjs.org/constructs/-/constructs-10.5.0.tgz", - "integrity": "sha512-zWjwqIgk4nAWmQGrPnDWv+M2Yly6m7ROyqmmQVLgJiHnWP762It22uWFaF2Pu/sSx0u8WsoUcvt0PZ4DQIQYwQ==", + "version": "10.5.1", + "resolved": "https://registry.npmjs.org/constructs/-/constructs-10.5.1.tgz", + "integrity": "sha512-f/TfFXiS3G/yVIXDjOQn9oTlyu9Wo7Fxyjj7lb8r92iO81jR2uST+9MstxZTmDGx/CgIbxCXkFXgupnLTNxQZg==", "dev": true, "license": "Apache-2.0" }, @@ -9380,9 +9411,9 @@ "license": "ISC" }, "node_modules/ink": { - "version": "6.7.0", - "resolved": "https://registry.npmjs.org/ink/-/ink-6.7.0.tgz", - "integrity": "sha512-dhB16KfdTO8yYwF2K0E4wPXpL88tdrjjB6w44AZ0ljSktYoUQQcxccq9KL1vpRhk8JIa0A7B7zvjajHqI42teA==", + "version": "6.8.0", + "resolved": "https://registry.npmjs.org/ink/-/ink-6.8.0.tgz", + "integrity": "sha512-sbl1RdLOgkO9isK42WCZlJCFN9hb++sX9dsklOvfd1YQ3bQ2AiFu12Q6tFlr0HvEUvzraJntQCCpfEoUe9DSzA==", "license": "MIT", "dependencies": { "@alcalzone/ansi-tokenize": "^0.2.4", @@ -9401,7 +9432,7 @@ "react-reconciler": "^0.33.0", "scheduler": "^0.27.0", "signal-exit": "^3.0.7", - "slice-ansi": "^7.1.0", + "slice-ansi": "^8.0.0", "stack-utils": "^2.0.6", "string-width": "^8.1.1", "terminal-size": "^4.0.1", @@ -9417,7 +9448,7 @@ "peerDependencies": { "@types/react": ">=19.0.0", "react": ">=19.0.0", - "react-devtools-core": "^6.1.2" + "react-devtools-core": ">=6.1.2" }, "peerDependenciesMeta": { "@types/react": { @@ -10548,6 +10579,23 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/log-update/node_modules/slice-ansi": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-7.1.2.tgz", + "integrity": "sha512-iOBWFgUX7caIZiuutICxVgX1SdxwAVFFKwt1EvMYYec/NWO5meOJ6K5uQxhrYBdQJne4KxiqZc+KptFOWFSI9w==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.2.1", + "is-fullwidth-code-point": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/slice-ansi?sponsor=1" + } + }, "node_modules/log-update/node_modules/string-width": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", @@ -10751,10 +10799,10 @@ } }, "node_modules/minipass": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", - "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", - "license": "ISC", + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", + "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", + "license": "BlueOak-1.0.0", "engines": { "node": ">=16 || 14 >=14.17" } @@ -12110,16 +12158,16 @@ } }, "node_modules/slice-ansi": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-7.1.2.tgz", - "integrity": "sha512-iOBWFgUX7caIZiuutICxVgX1SdxwAVFFKwt1EvMYYec/NWO5meOJ6K5uQxhrYBdQJne4KxiqZc+KptFOWFSI9w==", + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-8.0.0.tgz", + "integrity": "sha512-stxByr12oeeOyY2BlviTNQlYV5xOj47GirPr4yA1hE9JCtxfQN0+tVbkxwCtYDQWhEKWFHsEK48ORg5jrouCAg==", "license": "MIT", "dependencies": { - "ansi-styles": "^6.2.1", - "is-fullwidth-code-point": "^5.0.0" + "ansi-styles": "^6.2.3", + "is-fullwidth-code-point": "^5.1.0" }, "engines": { - "node": ">=18" + "node": ">=20" }, "funding": { "url": "https://github.com/chalk/slice-ansi?sponsor=1" @@ -13115,9 +13163,9 @@ } }, "node_modules/undici-types": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", - "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", + "version": "7.18.2", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz", + "integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==", "dev": true, "license": "MIT" }, @@ -13442,22 +13490,6 @@ } } }, - "node_modules/vitest/node_modules/yaml": { - "version": "2.8.2", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.2.tgz", - "integrity": "sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A==", - "extraneous": true, - "license": "ISC", - "bin": { - "yaml": "bin.mjs" - }, - "engines": { - "node": ">= 14.6" - }, - "funding": { - "url": "https://github.com/sponsors/eemeli" - } - }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", From 1eec0706eae739d9e2bcf19cf5b7a0815b023722 Mon Sep 17 00:00:00 2001 From: Jesse Turner Date: Thu, 19 Feb 2026 12:16:08 -0500 Subject: [PATCH 2/2] fix tui test fails by waiting escape key to work --- package-lock.json | 30 ++++++++++++++----- .../__tests__/FullScreenLogView.test.tsx | 3 +- .../__tests__/PromptScreen.test.tsx | 9 ++++-- .../tui/components/__tests__/Screen.test.tsx | 3 +- .../hooks/__tests__/useExitHandler.test.tsx | 6 ++-- 5 files changed, 37 insertions(+), 14 deletions(-) diff --git a/package-lock.json b/package-lock.json index 2fa025e3..e9d4ec5e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -111,19 +111,19 @@ "license": "Apache-2.0" }, "node_modules/@aws-cdk/aws-service-spec": { - "version": "0.1.152", - "resolved": "https://registry.npmjs.org/@aws-cdk/aws-service-spec/-/aws-service-spec-0.1.152.tgz", - "integrity": "sha512-NcplhfPPClcWWR9cm/Ph85c/3Mh9GVRikbHt6ZJY1ocUHqQqruiSw5OYaTfgXl0X9YvHboJgw7NvneYN5NK9Lw==", + "version": "0.1.153", + "resolved": "https://registry.npmjs.org/@aws-cdk/aws-service-spec/-/aws-service-spec-0.1.153.tgz", + "integrity": "sha512-FFy6TOWu2pGg6OYRbsH82n1crCgLivdm/FVlfahQp9sNa78uPU9Zx6Q76zg4fJ+nwd8XFHKYUSF59XMo0kw2ag==", "license": "Apache-2.0", "dependencies": { - "@aws-cdk/service-spec-types": "^0.0.218", + "@aws-cdk/service-spec-types": "^0.0.219", "@cdklabs/tskb": "^0.0.4" } }, "node_modules/@aws-cdk/aws-service-spec/node_modules/@aws-cdk/service-spec-types": { - "version": "0.0.218", - "resolved": "https://registry.npmjs.org/@aws-cdk/service-spec-types/-/service-spec-types-0.0.218.tgz", - "integrity": "sha512-ZsdQEUwDHc38tHD5mMJ2hDXnfP5HYeG+xj3VZESfGwbRZHHSUOIqNE0wRRnmD2MY0m9w6D6j/08M3ylB74Oh0w==", + "version": "0.0.219", + "resolved": "https://registry.npmjs.org/@aws-cdk/service-spec-types/-/service-spec-types-0.0.219.tgz", + "integrity": "sha512-634M1Hr3roDmFpLjEucskzZnWUpA+dMZ3HFbMDQ4EEf8BOZCy6hV8S1kP5fgQcivJJFAp1tDlfWEDXfvoQk50w==", "license": "Apache-2.0", "dependencies": { "@cdklabs/tskb": "^0.0.4" @@ -13490,6 +13490,22 @@ } } }, + "node_modules/vitest/node_modules/yaml": { + "version": "2.8.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.2.tgz", + "integrity": "sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A==", + "extraneous": true, + "license": "ISC", + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14.6" + }, + "funding": { + "url": "https://github.com/sponsors/eemeli" + } + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", diff --git a/src/cli/tui/components/__tests__/FullScreenLogView.test.tsx b/src/cli/tui/components/__tests__/FullScreenLogView.test.tsx index 887d3f9b..79647581 100644 --- a/src/cli/tui/components/__tests__/FullScreenLogView.test.tsx +++ b/src/cli/tui/components/__tests__/FullScreenLogView.test.tsx @@ -53,11 +53,12 @@ describe('FullScreenLogView', () => { expect(lastFrame()).toContain('No logs yet'); }); - it('calls onExit on Escape key', () => { + it('calls onExit on Escape key', async () => { const onExit = vi.fn(); const { stdin } = render(); stdin.write(ESCAPE); + await new Promise(resolve => setImmediate(resolve)); expect(onExit).toHaveBeenCalledTimes(1); }); diff --git a/src/cli/tui/components/__tests__/PromptScreen.test.tsx b/src/cli/tui/components/__tests__/PromptScreen.test.tsx index ada95f2c..d4930fd2 100644 --- a/src/cli/tui/components/__tests__/PromptScreen.test.tsx +++ b/src/cli/tui/components/__tests__/PromptScreen.test.tsx @@ -49,7 +49,7 @@ describe('PromptScreen', () => { expect(onConfirm).toHaveBeenCalledTimes(1); }); - it('calls onExit on Escape key', () => { + it('calls onExit on Escape key', async () => { const onExit = vi.fn(); const { stdin } = render( @@ -58,6 +58,7 @@ describe('PromptScreen', () => { ); stdin.write(ESCAPE); + await new Promise(resolve => setImmediate(resolve)); expect(onExit).toHaveBeenCalledTimes(1); }); @@ -200,11 +201,12 @@ describe('ErrorPrompt', () => { expect(onBack).toHaveBeenCalledTimes(1); }); - it('calls onExit on Escape key', () => { + it('calls onExit on Escape key', async () => { const onExit = vi.fn(); const { stdin } = render(); stdin.write(ESCAPE); + await new Promise(resolve => setImmediate(resolve)); expect(onExit).toHaveBeenCalledTimes(1); }); @@ -257,11 +259,12 @@ describe('ConfirmPrompt', () => { expect(onConfirm).toHaveBeenCalledTimes(1); }); - it('calls onCancel on Escape key', () => { + it('calls onCancel on Escape key', async () => { const onCancel = vi.fn(); const { stdin } = render(); stdin.write(ESCAPE); + await new Promise(resolve => setImmediate(resolve)); expect(onCancel).toHaveBeenCalledTimes(1); }); diff --git a/src/cli/tui/components/__tests__/Screen.test.tsx b/src/cli/tui/components/__tests__/Screen.test.tsx index 3d6bec19..707b2242 100644 --- a/src/cli/tui/components/__tests__/Screen.test.tsx +++ b/src/cli/tui/components/__tests__/Screen.test.tsx @@ -49,7 +49,7 @@ describe('Screen', () => { expect(lastFrame()).toContain('Press Enter to continue'); }); - it('calls onExit on Escape key', () => { + it('calls onExit on Escape key', async () => { const onExit = vi.fn(); const { stdin } = render( @@ -58,6 +58,7 @@ describe('Screen', () => { ); stdin.write(ESCAPE); + await new Promise(resolve => setImmediate(resolve)); expect(onExit).toHaveBeenCalledTimes(1); }); diff --git a/src/cli/tui/hooks/__tests__/useExitHandler.test.tsx b/src/cli/tui/hooks/__tests__/useExitHandler.test.tsx index ba819c28..b6b73ec6 100644 --- a/src/cli/tui/hooks/__tests__/useExitHandler.test.tsx +++ b/src/cli/tui/hooks/__tests__/useExitHandler.test.tsx @@ -14,11 +14,12 @@ function ExitHandlerHarness({ onExit, enabled }: { onExit: () => void; enabled?: } describe('useExitHandler', () => { - it('calls onExit when Escape is pressed', () => { + it('calls onExit when Escape is pressed', async () => { const onExit = vi.fn(); const { stdin } = render(); stdin.write(ESCAPE); + await new Promise(resolve => setImmediate(resolve)); expect(onExit).toHaveBeenCalledTimes(1); }); @@ -53,11 +54,12 @@ describe('useExitHandler', () => { expect(onExit).not.toHaveBeenCalled(); }); - it('enabled defaults to true', () => { + it('enabled defaults to true', async () => { const onExit = vi.fn(); const { stdin } = render(); stdin.write(ESCAPE); + await new Promise(resolve => setImmediate(resolve)); expect(onExit).toHaveBeenCalledTimes(1); });