# Write Tests for an AWS CDK App

Yes. An agent writes aws-cdk-lib/assertions tests and a snapshot for your CDK stack, runs them with no AWS account and opens a pull request.

[Run this on Rush](https://cloud.getrush.ai/run/write-cdk-assertion-tests/aws-cdk/), in a Project that has your repository.

## What the agent does

The agent clones the repository into its own cloud sandbox and reads the stack and its test setup. In `typescript/api-cors-lambda-crud-dynamodb` from aws-samples/aws-cdk-examples (`aws-cdk-lib` 2.262.1, `aws-cdk` 2.1133.0), everything is in `index.ts`: a DynamoDB `Table` named `items` with partition key `itemId` of type `STRING` and `RemovalPolicy.DESTROY`, five `NodejsFunction` Lambdas on `Runtime.NODEJS_24_X` that share `PRIMARY_KEY` and `TABLE_NAME` environment variables, a `RestApi` called `Items Service` with `/items` and `/items/{id}`, and an `addCorsOptions` helper that puts an `OPTIONS` mock integration on both resources. A `jest.config.js` already exists, using `@swc/jest` and `aws-cdk-lib/testhelpers/jest-autoclean`, but `package.json` has no `test` script and there is no test file.

Before writing a test it fixes the one thing that blocks testing. `index.ts` ends with `const app = new App(); new ApiLambdaCrudDynamoDBStack(app, ...); app.synth();` at module level, so importing the stack class into a test would also build and synthesize the whole app. The agent moves those three lines into a separate entry file and points `cdk.json`'s `app` command at it, or guards them, so the test can create its own `App` and stack. It adds `"test": "jest"` to `package.json`. The Lambdas bundle with esbuild during synth and read `lambdas/package-lock.json`, which the `postinstall` script creates by running `npm install` in `lambdas/`, so `npm install` has to run before the tests.

Then it writes the assertions with `Template.fromStack`. The table: `KeySchema` with `itemId` as `HASH`, `AttributeDefinitions` with type `S`, `TableName` `items`, and the billing mode, which the code never sets, so the template shows the provisioned default; the test pins what synthesizes today and the pull request points out that nothing chose it. Each Lambda: `resourceCountIs('AWS::Lambda::Function', 5)` plus `hasResourceProperties` for the runtime and the two environment variables, with `TABLE_NAME` matched as a reference to the table rather than a literal. The API: `GET` and `POST` on `/items`, `GET`, `PATCH` and `DELETE` on `/items/{id}`, and an `OPTIONS` method on each with the four `Access-Control-Allow-*` response parameters. Last, a Jest snapshot of the full template, with asset hashes normalised so a Lambda code change does not churn it.

The CORS test turns up a real mismatch: `Access-Control-Allow-Methods` is `'OPTIONS,GET,PUT,POST,DELETE'`, but the API has no `PUT` method and uses `PATCH` for updates, so a browser preflight for an update would be refused. The agent writes the test to assert today's header and reports the mismatch in the pull request instead of editing the stack. It runs `npm test` until it passes; nothing needs AWS credentials, because synthesis is local.

## Start it with one API call

`POST https://api.prix.dev/o/{org}/sessions`

```json
{
  "agent": "claude",
  "input": "In typescript/api-cors-lambda-crud-dynamodb, write Jest tests with aws-cdk-lib/assertions for the synthesized template. If index.ts synthesizes the app at import time, move the App construction and synth into a separate entry file and update cdk.json so tests can build their own stack. Add a test script to package.json that uses the existing jest.config.js. Assert the table's key schema, attribute types, table name and billing mode as synthesized; each Lambda's runtime and environment variables, with TABLE_NAME matched as a reference to the table; every API resource and method; and the CORS OPTIONS method and its Access-Control-Allow-* headers on each resource. Add a snapshot of the full template with asset hashes normalised. Do not change the stack's resources; if a test shows something inconsistent, assert today's value and report it. Run npm install and npm test until it passes, with no AWS credentials, and open a pull request.",
  "resources": [
    {
      "url": "https://github.com/your-org/your-app"
    }
  ]
}
```

1. **Get an API key.** In the console, open Settings, then API keys, and create a key for your org. Send it in the Authorization header as a Bearer token.
2. **Send the request.** POST the body in request.json to https://api.prix.dev/o/{org}/sessions, with your repository as the resources URL. The run starts in its own cloud sandbox.
3. **Read what came back.** Follow the run on its live stream or wait for the run.completed webhook, then read the result and its trajectory.

## What comes back

What comes back is a pull request on its own branch. For an example like this one, the diff adds a test file such as `test/stack.test.ts` with its `__snapshots__` folder, a `test` script in `package.json`, and the small split of `index.ts` that lets a test build the stack without synthesizing the app, with `cdk.json` updated to match. The description lists each assertion group (table, functions, API, CORS, snapshot) and reports what the tests surfaced: the `PUT` in the CORS methods where the API uses `PATCH`, and a billing mode that comes from the default rather than a decision.

The session transcript keeps each command with its output: `npm install` including the `postinstall` in `lambdas/`, the esbuild bundling during synth, and every `npm test` run. The pull request's checks then repeat it on GitHub: aws-cdk-examples' `build-pull-request.yml` builds each changed TypeScript example with `scripts/build-typescript.sh`, which installs, runs `build`, runs `npm run --if-present test` and synthesizes. Today that test step does nothing for this example because there is no `test` script; once the pull request adds one, the new tests run on every later change to it.

Before merging, a reviewer checks that the assertions say what the team means rather than what the template happens to contain, decides whether to fix the CORS header in a follow-up change, and confirms the snapshot ignores asset hashes so it changes only when the infrastructure does.

## Run it on every new CDK stack

In a monorepo with many stacks, send one `POST /o/{org}/sessions` per stack with its directory named in `input` and the repository in `resources`. Each run gets its own sandbox and branch; a key runs at most its `max_concurrent` sessions at once (4 by default), so a script starts the next when one finishes.

With a `webhook_url` on the key, Rush posts `run.completed` or `run.failed` when each run's turn finishes. The session read, `GET /o/{org}/sessions/{id}`, carries the repository and pull request, so your script can collect the links for review.

## Why run it on Rush?

Assertion tests need the stack to synthesize, and synthesis here means installing two `package.json` trees, bundling five Lambdas with esbuild and running Jest with `@swc/jest`, all without AWS credentials. In a Rush sandbox the agent does that on a clean machine with no AWS profile on it, which also proves the tests do not quietly depend on one.

The transcript keeps every install and test run with its output, so a reviewer sees the synthesized resources the assertions were written against and the run where they pass. The findings, like the CORS methods mismatch, come with the test that exposed them.

## Frequently asked questions

### Does it need my AWS account or credentials?

No. `Template.fromStack` synthesizes the stack locally and the assertions read the resulting CloudFormation template. Nothing is deployed and no AWS API is called, and Rush never asks for AWS credentials for this job.

### Why does it change index.ts if the prompt says not to change the stack?

Only the entry point moves. When `index.ts` creates an `App` and calls `app.synth()` at module level, any test that imports the stack class runs that too. Moving those lines to their own file leaves every resource the same and lets the test build its own stack.

### Will the snapshot break every time a Lambda changes?

Not if asset hashes are normalised, which the prompt asks for. The snapshot then changes when the infrastructure changes, a new resource or a changed property, and a code-only Lambda change shows up in the Lambda's own tests instead.

### Does it work for Python CDK apps?

Yes. The same `assertions` module exists in Python as `aws_cdk.assertions`, and the agent writes pytest tests with `Template.from_stack` instead. Point `input` at the directory and say which language.
