# Add Missing Tests to Every Pull Request in an ASP.NET Core App

Yes. An agent reads each pull request, writes tests for the ordering and basket code it changes, runs dotnet test and pushes them to the same branch.

[Run this on Rush](https://cloud.getrush.ai/run/test-every-pull-request/aspnet-core/), in a Project that has your repository.

## What the agent does

The agent checks out the pull request's branch and reads its diff against the base branch before it writes anything. In an ASP.NET Core solution like dotnet/eShop the diff usually lands in `src/Ordering.API` (the minimal API endpoints in `Apis/OrdersApi.cs`, the commands and handlers under `Application/`), in `src/Ordering.Domain` (the `Order` and `Buyer` aggregates) or in `src/Basket.API/Grpc/BasketService.cs`. For each changed method it looks for a test that already exercises the new branch or the new rule, and lists the behavior that has none.

Then it reads how the solution already tests that code, because eShop uses two frameworks side by side. `global.json` pins SDK `10.0.302`, sets the test runner to `Microsoft.Testing.Platform` and pins `MSTest.Sdk` 4.0.2. The unit test projects, `tests/Basket.UnitTests` and `tests/Ordering.UnitTests`, are `MSTest.Sdk` projects that substitute dependencies with NSubstitute 5.3: `BasketServiceTests.cs` builds a `BasketService` over `Substitute.For<IBasketRepository>()` and a `TestServerCallContext`. The functional projects, `tests/Ordering.FunctionalTests` and `tests/Basket.FunctionalTests`, are Aspire app hosts that use `xunit.v3.mtp-v2`, a `WebApplicationFactory<Program>` in `OrderingApiFixture`, and a Redis container in `RedisFixture`. The agent writes each new test in the project and the framework that already covers its neighbor, so a domain rule gets an MSTest `[TestMethod]` next to `OrderAggregateTest.cs` and a new HTTP behavior gets an xUnit `[Fact]` in `OrderingApiTests.cs`.

It reuses what the projects already provide instead of building new scaffolding: the `Builders.cs` helpers in `Ordering.UnitTests` for orders and addresses, the `Helpers` folder in `Basket.UnitTests` for gRPC call contexts, and the `x-requestid` header pattern the functional tests already send to the cancel and ship endpoints. Each test names the behavior it pins, such as rejecting a cancel with an empty request id, rather than the method it calls.

Before pushing, it runs `dotnet test` on the projects it touched and reads the output. A new test that fails because the application code is wrong is not rewritten until it passes: the prompt forbids changing application code, so the agent reports the failure as a finding instead. The functional tests start containers through Aspire, and the repository's own `tests/README.md` says they need Docker running; if the sandbox cannot start them, the agent says which tests it could not run instead of pushing them as passing.

## Start it with one API call

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

```json
{
  "agent": "claude",
  "input": "This pull request changes the ordering and basket APIs. Read the diff against the base branch and list the new or changed behavior in src/Ordering.API, src/Ordering.Domain and src/Basket.API that no existing test covers. Write tests for each item in the test project that already covers its neighbor: MSTest with NSubstitute in tests/Ordering.UnitTests and tests/Basket.UnitTests, xUnit with the existing fixtures in tests/Ordering.FunctionalTests and tests/Basket.FunctionalTests. Reuse Builders.cs and the existing helpers. Run dotnet test on each project you changed until the new tests pass. Do not change application code: if a test fails because the code is wrong, keep the test out of the commit and describe the failure. Push the tests to this pull request's branch and post a comment listing each test, the behavior it covers and the projects you ran.",
  "resources": [
    {
      "url": "https://github.com/your-org/your-app/pull/123"
    }
  ]
}
```

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 one more commit on the pull request's own branch, not a second pull request. In a solution shaped like eShop it touches only files under `tests/`: new `[TestMethod]` cases in `Ordering.UnitTests/Domain` or `Application`, new cases in `BasketServiceTests.cs`, or new `[Fact]` methods in `OrderingApiTests.cs`, plus any small builder it had to extend in `Builders.cs`. Nothing under `src/` changes, so the diff the author asked to be reviewed stays the same.

The pull request also gets a comment from the agent that lists each test by name, the behavior it covers, the file it lives in and the `dotnet test` projects it ran. When a test exposed a real bug, the comment describes the failing assertion and the line in the application code, and that test is left out of the commit. The repository's own CI then runs on the new commit, so the checks on the pull request are the proof the tests pass outside the sandbox too.

Before merging, a reviewer reads the new tests the way they read any test: does each assertion check the behavior the author meant, or only what the code happens to do today? They also check that substitutes stand in only for the repository, event bus or identity boundaries, and that no test depends on ordering or a fixed clock that will break later.

## Run it on every pull request opened

In the Project bound to the repository, add an automation with this prompt and a GitHub trigger on `pull_request.opened`. Each new pull request then starts its own run, and the run's prompt carries the event: the repository and pull request number, its URL, title, author and base branch. Set `branch_patterns` to `main` so only pull requests into your default branch fire it, and `max_runs_per_hour` to cap a busy day. A pull request fires only when its author can write to the repository; pull requests from forks never fire, and neither do pull requests the Rush GitHub App opened itself.

If you would rather drive it from your own server, point a GitHub webhook for `pull_request` at it and, on `opened`, send this request with the pull request's URL in `resources`, which clones that pull request's branch. Use the delivery id as the `Idempotency-Key` header so a redelivered webhook returns the run it already started.

## Why run it on Rush?

On a laptop this job means checking out someone else's branch, restoring a .NET 10 solution with a dozen projects, and leaving Docker running for the Aspire functional tests while you write and rerun them. On Rush each pull request gets its own sandbox with its own clone at that branch, so the agent can run `dotnet test` as many times as it needs without touching your working tree, and two pull requests opened in the same hour do not wait for each other.

The result is a commit you can check, not a claim that coverage went up. The trajectory keeps every `dotnet test` command and its output, so a reviewer can see which projects ran, which tests failed on the way and why the agent left one out. The repository's CI runs again on the pushed commit, and the tests sit in the same diff the reviewer is already reading.

## Frequently asked questions

### Will it write MSTest or xUnit tests?

Both, matching the project it adds to. eShop's unit test projects use `MSTest.Sdk`, and its functional test projects use `xunit.v3.mtp-v2`, so a test in `Ordering.UnitTests` is a `[TestMethod]` and a test in `Ordering.FunctionalTests` is a `[Fact]`. It does not add a third framework or move existing tests.

### Can it change my application code to make a test pass?

The prompt tells it not to, and the pushed commit only touches files under `tests/`. When a new test fails because the code under review is wrong, it leaves that test out and describes the failure in its comment, which is usually the most useful thing the run finds.

### Do the Aspire functional tests run in the sandbox?

They need Docker for the Postgres and Redis containers Aspire starts, as eShop's `tests/README.md` says. When the containers cannot start, the agent runs the unit test projects, says which functional tests it wrote but could not run, and leaves the final word to your CI.

### What happens when the author pushes more commits?

The trigger in this page fires on `pull_request.opened` only. To cover later pushes as well, add `pull_request.synchronize` to the same trigger; each push then starts a run that reads the new commits and adds tests for what they changed.
