Skip to content

Commit b347a7b

Browse files
authored
feat: testing fixes as well as refactoring test organization and to use npm & tsx (#3)
* test: add unit tests for validation, update, errors, and zod-util - src/cli/commands/add/validate.test.ts - src/cli/commands/update/update.test.ts - src/cli/errors.test.ts - src/schema/schemas/zod-util.test.ts 🤖 Assisted by Amazon Q Developer * refactor: move CLI integration tests from __tests__ to integ-tests - Move help.test.ts and json-output.test.ts to integ-tests/ - Fix import paths for test-utils - Remove empty src/cli/__tests__/ directory These tests run the CLI as a black box and belong with other integration tests, not colocated with source files. * refactor: migrate from bun to esbuild/tsx/vitest\n\nBuild system:\n- Replace bun bundler with esbuild (esbuild.config.mjs)\n- Delete bun.build.ts\n\nRuntime:\n- Replace bun with tsx for TypeScript execution\n- Update dev server to use npx tsx watch\n- Update test runner to use node with built bundle\n\nTesting:\n- Replace bun:test with vitest\n- Migrate all assertions from node:assert to vitest expect\n- Move 29 test files to __tests__/ directories\n- Add vitest.config.ts with text-loader plugin\n\nDependencies:\n- Add: esbuild, tsx, vitest\n- Remove: @types/bun\n\nDocumentation:\n- Update CONTRIBUTING.md (remove bun prereq, add Testing section)\n- Update integ-tests/README.md (vitest commands/imports)\n- Add docs/TESTING.md\n\nConfig:\n- Update tsconfig.build.json to exclude test files\n- Add test and test:watch npm scripts * fix: prefix Lambda template tool names to allow multiple behind-gateway tools\n\nWhen adding multiple behind-gateway Lambda tools, the hardcoded tool names\n(lookup_ip, get_random_user, fetch_post) would conflict in mcp-defs.json.\n\nNow tool names are prefixed with the MCP tool name (e.g., MyTool_lookup_ip)\nto avoid conflicts and allow multiple Lambda tools per project. * fix: improve invoke command CLI mode detection and validation\n\n- Detect CLI mode when --json, --target, or --stream flags are provided\n (follows the same pattern as deploy command)\n- Add validation for streaming mode requiring a prompt\n- Prevents unexpected TUI mode when user intends CLI usage * fix: add optional sessionId field to AgentCoreDeployedStateSchema * fix: resolve ESLint and TypeScript errors\n\n- Prefix unused parameters with underscore\n- Add null checks for optional values\n- Use String() for template expressions with unknown types\n- Add type assertions for exhaustive switch cases\n- Fix type imports * chore: remove tests for unimplemented --policy flag\n\nThese tests were for a --policy flag on remove commands that was never\nimplemented. The tests used --policy cascade and --policy restrict\nwhich are not valid CLI options. * test: fix remove command tests\n\n- Use alphanumeric names (schema requires no hyphens)\n- Update tests to expect cascade behavior (default removal policy)\n- Add result checks for add commands before testing remove\n- Fix field name (agentCoreGateways not gateways) * test: fix various test issues\n\n- Remove target requirement test (target defaults to 'default')\n- Skip plan test requiring CDK dependencies (integration test)\n- Remove incorrect synth.lock cleanup test (intentionally not cleaned)\n- Fix import paths and add non-null assertions\n- Add cwd argument to runCLI calls * style: apply prettier formatting to test files and imports * fix: improve integration test infrastructure\n\n- Add LOCAL_L3_PATH env var support for local L3 constructs development\n- Separate integration tests into vitest.integ.config.ts with 120s timeout\n- Fix dev-server test: use built bundle, clear INIT_CWD env var\n- Add test:integ npm script for running integration tests * fix: restore TypeScript (coming soon) disabled state * fix: pass disabled property to SelectList for language options * chore: remove test-output.txt
1 parent b60d76d commit b347a7b

File tree

100 files changed

+6085
-3658
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

100 files changed

+6085
-3658
lines changed

CONTRIBUTING.md

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,6 @@ public github issue.
6363
### Prerequisites
6464

6565
- Node.js 20+
66-
- Bun (for CLI bundling)
6766
- npm
6867

6968
### Building
@@ -73,6 +72,15 @@ npm install
7372
npm run build
7473
```
7574

75+
### Testing
76+
77+
```bash
78+
npm test # Run all tests
79+
npm run test:watch # Run tests in watch mode
80+
```
81+
82+
See [docs/TESTING.md](docs/TESTING.md) for detailed testing guidelines.
83+
7684
### Local Development with CDK Package
7785

7886
If you're also developing the CDK package (`@aws/agentcore-l3-cdk-constructs`):

docs/TESTING.md

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
# Testing Guide
2+
3+
## Quick Start
4+
5+
```bash
6+
npm test # Run all tests
7+
npm run test:watch # Run tests in watch mode
8+
```
9+
10+
## Test Organization
11+
12+
### Unit Tests
13+
14+
Unit tests are co-located with source files in `__tests__/` directories:
15+
16+
```
17+
src/cli/commands/add/
18+
├── action.ts
19+
├── command.ts
20+
└── __tests__/
21+
└── add.test.ts
22+
```
23+
24+
### Integration Tests
25+
26+
Integration tests live in `integ-tests/`:
27+
28+
```
29+
integ-tests/
30+
├── create-no-agent.test.ts
31+
├── create-with-agent.test.ts
32+
├── deploy.test.ts
33+
└── ...
34+
```
35+
36+
See [integ-tests/README.md](../integ-tests/README.md) for integration test details.
37+
38+
## Writing Tests
39+
40+
### Imports
41+
42+
Use vitest for all test utilities:
43+
44+
```typescript
45+
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
46+
```
47+
48+
### Assertions
49+
50+
Use `expect` assertions:
51+
52+
```typescript
53+
// Equality
54+
expect(result).toBe('expected');
55+
expect(obj).toEqual({ key: 'value' });
56+
57+
// Truthiness
58+
expect(value).toBeTruthy();
59+
expect(value).toBeFalsy();
60+
61+
// Errors
62+
expect(() => fn()).toThrow();
63+
expect(() => fn()).toThrow('message');
64+
```
65+
66+
### Mocking
67+
68+
Use `vi` for mocks:
69+
70+
```typescript
71+
// Mock functions
72+
const mockFn = vi.fn();
73+
mockFn.mockReturnValue('value');
74+
mockFn.mockResolvedValue('async value');
75+
76+
// Spies
77+
vi.spyOn(module, 'method');
78+
79+
// Module mocks
80+
vi.mock('./module');
81+
```
82+
83+
## Test Utilities
84+
85+
### CLI Runner
86+
87+
`src/test-utils/cli-runner.ts` runs CLI commands in tests:
88+
89+
```typescript
90+
import { runCLI } from '../src/test-utils/cli-runner';
91+
92+
const result = await runCLI(['create', '--name', 'test'], tempDir);
93+
expect(result.exitCode).toBe(0);
94+
```
95+
96+
## Configuration
97+
98+
Test configuration is in `vitest.config.ts`:
99+
100+
- Test timeout: 15 seconds
101+
- Hook timeout: 60 seconds
102+
- Test patterns: `src/**/*.test.ts`, `integ-tests/**/*.test.ts`
103+
104+
## Integration Tests
105+
106+
Integration tests require:
107+
108+
- AWS credentials configured
109+
- IAM permissions for CloudFormation operations
110+
- Dedicated test AWS account (recommended)
111+
112+
Run integration tests:
113+
114+
```bash
115+
npm run test:integ
116+
```

bun.build.ts renamed to esbuild.config.mjs

Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
1+
import * as esbuild from 'esbuild';
12
import * as fs from 'fs';
3+
import { createRequire } from 'module';
24

35
// Stub plugin for optional dev dependencies
46
const optionalDepsPlugin = {
57
name: 'optional-deps',
6-
setup(build: Parameters<Parameters<typeof Bun.build>[0]['plugins'][number]['setup']>[0]) {
8+
setup(build) {
79
// Stub react-devtools-core (only used when DEV=true)
810
build.onResolve({ filter: /^react-devtools-core$/ }, () => ({
911
path: 'react-devtools-core',
@@ -19,17 +21,18 @@ const optionalDepsPlugin = {
1921
// Text loader plugin for embedding files
2022
const textLoaderPlugin = {
2123
name: 'text-loader',
22-
setup(build: Parameters<Parameters<typeof Bun.build>[0]['plugins'][number]['setup']>[0]) {
24+
setup(build) {
25+
// Handle .md and .txt files as text
2326
build.onLoad({ filter: /\.(md|txt)$/ }, async args => {
24-
const text = await Bun.file(args.path).text();
27+
const text = await fs.promises.readFile(args.path, 'utf8');
2528
return {
2629
contents: `export default ${JSON.stringify(text)};`,
2730
loader: 'js',
2831
};
2932
});
30-
// Handle .ts files in llm-compacted as text (use platform-agnostic pattern)
33+
// Handle .ts files in llm-compacted as text
3134
build.onLoad({ filter: /llm-compacted[/\\].*\.ts$/ }, async args => {
32-
const text = await Bun.file(args.path).text();
35+
const text = await fs.promises.readFile(args.path, 'utf8');
3336
return {
3437
contents: `export default ${JSON.stringify(text)};`,
3538
loader: 'js',
@@ -38,13 +41,17 @@ const textLoaderPlugin = {
3841
},
3942
};
4043

41-
await Bun.build({
42-
entrypoints: ['./src/cli/index.ts'],
43-
outdir: './dist/cli',
44-
target: 'node',
44+
await esbuild.build({
45+
entryPoints: ['./src/cli/index.ts'],
46+
outfile: './dist/cli/index.mjs',
47+
bundle: true,
48+
platform: 'node',
4549
format: 'esm',
4650
minify: true,
47-
naming: '[dir]/[name].mjs',
51+
// Inject require shim for ESM compatibility with CommonJS dependencies
52+
banner: {
53+
js: `import { createRequire } from 'module'; const require = createRequire(import.meta.url);`,
54+
},
4855
external: ['fsevents', '@aws-cdk/toolkit-lib'],
4956
plugins: [optionalDepsPlugin, textLoaderPlugin],
5057
});

integ-tests/README.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ This directory contains real AWS integration tests that actually deploy resource
1515
npm run test:integ
1616

1717
# Run a specific test
18-
bun test --timeout 300000 integ-tests/integ.deploy.ts
18+
npx vitest run integ-tests/deploy.test.ts --testTimeout=300000
1919
```
2020

2121
## Test Naming Convention
@@ -38,14 +38,14 @@ Integration tests are NOT run automatically on every PR. They can be triggered:
3838
## Writing Integration Tests
3939

4040
```typescript
41-
import { runCLI } from '../src/test-utils';
42-
import { after, before, describe, it } from 'node:test';
41+
import { runCLI } from '../src/test-utils/cli-runner';
42+
import { afterAll, describe, expect, it } from 'vitest';
4343

4444
describe('integ: deploy', () => {
4545
// Use unique stack names to avoid conflicts
4646
const stackName = `test-${Date.now()}`;
4747

48-
after(async () => {
48+
afterAll(async () => {
4949
// ALWAYS clean up - destroy the stack
5050
await runCLI(['destroy', '--target', stackName, '--force'], projectDir);
5151
});

integ-tests/create-no-agent.test.ts

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
11
import { exists, runCLI } from '../src/test-utils/index.js';
2-
import { afterAll, beforeAll, describe, it } from 'bun:test';
3-
import assert from 'node:assert';
42
import { execSync } from 'node:child_process';
53
import { randomUUID } from 'node:crypto';
64
import { mkdir, rm } from 'node:fs/promises';
75
import { tmpdir } from 'node:os';
86
import { join } from 'node:path';
7+
import { afterAll, beforeAll, describe, expect, it } from 'vitest';
98

109
function hasCommand(cmd: string): boolean {
1110
try {
@@ -35,22 +34,22 @@ describe('integration: create without agent', () => {
3534
const name = `IntegNoAgent${Date.now()}`;
3635
const result = await runCLI(['create', '--name', name, '--no-agent', '--json'], testDir, false);
3736

38-
assert.strictEqual(result.exitCode, 0, `stderr: ${result.stderr}`);
37+
expect(result.exitCode, `stderr: ${result.stderr}`).toBe(0);
3938

4039
const json = JSON.parse(result.stdout);
41-
assert.strictEqual(json.success, true);
40+
expect(json.success).toBe(true);
4241

4342
// Verify npm install ran (in CDK project directory)
44-
assert.ok(
43+
expect(
4544
await exists(join(json.projectPath, 'agentcore', 'cdk', 'node_modules')),
4645
'agentcore/cdk/node_modules/ should exist'
47-
);
46+
).toBeTruthy();
4847

4948
// Verify git init ran
50-
assert.ok(await exists(join(json.projectPath, '.git')), '.git/ should exist');
49+
expect(await exists(join(json.projectPath, '.git')), '.git/ should exist').toBeTruthy();
5150

5251
// Verify at least one commit
5352
const gitLog = execSync('git log --oneline', { cwd: json.projectPath, encoding: 'utf-8' });
54-
assert.ok(gitLog.trim().length > 0, 'Should have at least one commit');
53+
expect(gitLog.trim().length > 0, 'Should have at least one commit').toBeTruthy();
5554
});
5655
});

integ-tests/create-with-agent.test.ts

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
11
import { exists, runCLI } from '../src/test-utils/index.js';
2-
import { afterAll, beforeAll, describe, it } from 'bun:test';
3-
import assert from 'node:assert';
42
import { execSync } from 'node:child_process';
53
import { randomUUID } from 'node:crypto';
64
import { mkdir, rm } from 'node:fs/promises';
75
import { tmpdir } from 'node:os';
86
import { join } from 'node:path';
7+
import { afterAll, beforeAll, describe, expect, it } from 'vitest';
98

109
function hasCommand(cmd: string): boolean {
1110
try {
@@ -53,19 +52,22 @@ describe('integration: create with Python agent', () => {
5352
false
5453
);
5554

56-
assert.strictEqual(result.exitCode, 0, `stderr: ${result.stderr}`);
55+
expect(result.exitCode, `stderr: ${result.stderr}`).toBe(0);
5756

5857
const json = JSON.parse(result.stdout);
59-
assert.strictEqual(json.success, true);
58+
expect(json.success).toBe(true);
6059

6160
// Verify npm install ran
62-
assert.ok(await exists(join(json.projectPath, 'agentcore', 'cdk', 'node_modules')), 'node_modules/ should exist');
61+
expect(
62+
await exists(join(json.projectPath, 'agentcore', 'cdk', 'node_modules')),
63+
'node_modules/ should exist'
64+
).toBeTruthy();
6365

6466
// Verify git init ran
65-
assert.ok(await exists(join(json.projectPath, '.git')), '.git/ should exist');
67+
expect(await exists(join(json.projectPath, '.git')), '.git/ should exist').toBeTruthy();
6668

6769
// Verify uv venv ran - .venv in app/{agentName} directory
6870
const agentDir = join(json.projectPath, 'app', json.agentName || name);
69-
assert.ok(await exists(join(agentDir, '.venv')), '.venv/ should exist in agent directory');
71+
expect(await exists(join(agentDir, '.venv')), '.venv/ should exist in agent directory').toBeTruthy();
7072
});
7173
});

integ-tests/deploy.test.ts

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
11
import { runCLI } from '../src/test-utils/index.js';
2-
import { afterAll, beforeAll, describe, it } from 'bun:test';
3-
import assert from 'node:assert';
42
import { execSync } from 'node:child_process';
53
import { randomUUID } from 'node:crypto';
64
import { mkdir, rm } from 'node:fs/promises';
75
import { tmpdir } from 'node:os';
86
import { join } from 'node:path';
7+
import { afterAll, beforeAll, describe, expect, it } from 'vitest';
98

109
function hasCommand(cmd: string): boolean {
1110
try {
@@ -89,17 +88,17 @@ describe('integration: deploy', () => {
8988
const result = await runCLI(['destroy', '--target', targetName, '--yes', '--json'], projectPath, false);
9089

9190
// Assert destroy succeeded
92-
assert.strictEqual(result.exitCode, 0, `Destroy failed: ${result.stderr}`);
91+
expect(result.exitCode, `Destroy failed: ${result.stderr}`).toBe(0);
9392
const json = JSON.parse(result.stdout);
94-
assert.strictEqual(json.success, true, 'Destroy should report success');
93+
expect(json.success, 'Destroy should report success').toBe(true);
9594
}
9695
await rm(testDir, { recursive: true, force: true });
9796
}, 120000);
9897

9998
it.skipIf(!hasNpm || !hasGit || !hasUv || !hasAws)(
10099
'deploys to AWS successfully',
101100
async () => {
102-
assert.ok(projectPath, 'Project should have been created');
101+
expect(projectPath, 'Project should have been created').toBeTruthy();
103102

104103
const result = await runCLI(['deploy', '--target', targetName, '--yes', '--json'], projectPath, false);
105104

@@ -108,10 +107,10 @@ describe('integration: deploy', () => {
108107
console.log('Deploy stderr:', result.stderr);
109108
}
110109

111-
assert.strictEqual(result.exitCode, 0, `Deploy failed: ${result.stderr}`);
110+
expect(result.exitCode, `Deploy failed: ${result.stderr}`).toBe(0);
112111

113112
const json = JSON.parse(result.stdout);
114-
assert.strictEqual(json.success, true, 'Deploy should report success');
113+
expect(json.success, 'Deploy should report success').toBe(true);
115114
},
116115
180000
117116
);

0 commit comments

Comments
 (0)