# Write Tests for an Actix Web API

Yes. An agent writes actix_web::test handler tests against a real Postgres in its sandbox, covers success and error responses and opens a pull request.

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

## What the agent does

The agent clones the repository into its own cloud sandbox and reads the service end to end. In the `databases/postgres` crate of actix/examples (package `db-postgres`, Actix Web 4.11 from the workspace, edition 2024, `rust-version = "1.98"`), `src/main.rs` mounts one resource, `/users`, with `POST` routed to `add_user` and `GET` to `get_users`. Both take the pool as `web::ThinData<Pool>`, a `deadpool_postgres` pool built from `ExampleConfig` in `src/config.rs`, which `confik` fills from `PG__USER`, `PG__PASSWORD`, `PG__HOST`, `PG__PORT`, `PG__DBNAME` and `PG__POOL__MAX_SIZE` environment variables. The SQL lives in `sql/add_user.sql`, `sql/get_users.sql` and `sql/schema.sql`, which drops and recreates a `testing` schema with a `users` table whose `username` is `UNIQUE NOT NULL`.

Then it lists the responses each handler can produce, from the code rather than from guesses. `add_user` returns `200` with the inserted row as JSON. A malformed body or a missing field is rejected by the `web::Json<User>` extractor with a `400` before the handler runs. A second user with the same username hits the unique constraint, comes back as `MyError::PGError`, and `ResponseError` in `src/errors.rs` maps that to an empty `500`, which the README describes. A pool that cannot connect maps to `MyError::PoolError`, a `500` whose body is the error text. `get_users` returns `200` with an empty array on an empty table and every row after inserts. `db.rs` also calls `.unwrap()` on `client.prepare`, so a missing schema panics instead of returning an error; the agent writes that down as a finding.

It starts Postgres in the sandbox and sets it up the way the README says: create `test_user`, create `testing_db` owned by it, run `psql -f sql/schema.sql testing_db`, and grant privileges on the `testing` schema, its tables and its sequences. The tests use `actix_web::test::init_service` with the same `App` the binary builds, `TestRequest::post().uri("/users").set_json(...)`, and `call_service` or `call_and_read_body_json` to check the status and body. Because the crate is a binary with no `lib.rs`, the agent puts the app setup in a function that both `main` and the tests call, so the tests exercise the real routes instead of a copy.

Last, it runs `cargo test -p db-postgres` until it passes, then `cargo clippy --tests -- -D warnings`, which the repository's `clippy-fmt.yml` workflow runs for the whole workspace. It also reads `.github/workflows/ci.yml` before pushing: that job installs `libpq-dev` and starts Redis but no Postgres server, and runs `cargo test --workspace`. So the agent either marks the database tests to skip when `PG__HOST` is unset, or adds a Postgres service to that job, and states in the pull request which one it did.

## Start it with one API call

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

```json
{
  "agent": "claude",
  "input": "In databases/postgres, write tests with actix_web::test for every handler. Read src/main.rs, src/db.rs, src/errors.rs and the sql/ files first and list every response each handler can return: success, extractor rejections such as malformed JSON or a missing field, a duplicate username, and a database that cannot be reached. Start Postgres in the sandbox, create the user and database and apply sql/schema.sql the way the README describes, and run the tests against that real database, not a mock. Build the App for tests from the same setup function main uses. Run cargo test -p db-postgres and cargo clippy --tests -- -D warnings until both pass. The workspace CI runs cargo test with no Postgres server, so make sure the database tests do not break it, and say in the pull request how you handled that. Report anything in the handlers that panics instead of returning an error. 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 a crate like `db-postgres`, the diff moves the `App` construction into a function `main` calls, adds a test module (in `src/main.rs` or under `tests/`) with one test per response the handlers can give, and either adds a skip when no database is configured or adds a Postgres service to `.github/workflows/ci.yml`. The description lists each test against the status it checks and names the panic on `client.prepare(...).unwrap()` as a finding rather than a fix.

The session transcript keeps every command with its output: Postgres starting, the `createuser`, `createdb` and `psql -f sql/schema.sql` setup, each `cargo test` run including the ones that failed while the tests were being written, and the clippy run. The pull request's checks run the workspace CI and the clippy and fmt workflow on GitHub.

Before merging, a reviewer checks that the tests reset the `testing` schema between runs so they do not depend on order, that the duplicate-username test asserts the `500` the code returns today, and that the choice made for CI (skip or service) is the one the maintainers want.

## Run it on every new Actix service

In a repository with many small services, the same request runs once per crate: change the crate path in `input` and send one `POST /o/{org}/sessions` per service, each in its own sandbox with its own Postgres. A key runs at most its `max_concurrent` sessions at once (4 by default), and one more answers `429`, so a script starts the next when one finishes.

Set a `webhook_url` on the key and Rush posts `run.completed` or `run.failed` as each run's turn ends, with a `session_url` that returns the pull request link.

## Why run it on Rush?

Handler tests against a real database need a Postgres server, a role, a database, a schema and grants, all of which have to be created and torn down without touching anyone's local data. In a Rush sandbox the agent installs and starts Postgres on a machine that exists for this run, applies `sql/schema.sql` there, and runs `cargo test` against it, so the duplicate-username case hits a real unique constraint rather than a mocked error.

The record is the proof: the transcript shows every `cargo test` and clippy run with its output, so a reviewer sees which tests failed first and why, and the pull request's checks show whether the workspace CI still passes with the new tests in it.

## Frequently asked questions

### Will the tests need a database in my CI?

Only if you want them to run there. The workspace CI in actix/examples has no Postgres service, so the agent either skips the database tests when no `PG__HOST` is set or adds a Postgres service to the job, and the pull request says which. Name your preference in `input` to decide it up front.

### Does it test through HTTP or by calling functions?

Through the app. `actix_web::test::init_service` builds the same `App` with the same routes, and `TestRequest` sends real requests through the extractors and error handling, so a `400` from `web::Json` or a `500` from `ResponseError` is tested as a client would see it.

### Will it change the handlers' error responses?

No. The tests assert what the code returns today, including the empty `500` for a duplicate username. Anything that looks wrong, such as a panic on `unwrap()`, is listed in the pull request for you to change deliberately.

### Does it work with sqlx or Diesel instead of tokio-postgres?

Yes. The agent reads whichever database layer the crate uses, runs its migrations against a real database in the sandbox, and builds the app for tests the same way. Name the crate path in `input`.
