# Add Missing Tests to Every Pull Request in a Go Service

Yes. An agent reads each pull request to your Go service, writes tests for the users and articles packages it changes, runs go test ./... and pushes them.

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

## What the agent does

The agent checks out the pull request and reads which functions it changes in each package. In a Go service like gothinkster/golang-gin-realworld-example-app the HTTP handlers live in `users/routers.go` (`UsersRegistration`, `UsersLogin`, `UserUpdate`, `ProfileFollow`, `ProfileUnfollow`) and `articles/routers.go` (`ArticleCreate`, `ArticleList`, `ArticleFeed`, `ArticleUpdate`, `ArticleDelete`, the favorite and comment handlers), with validation in each package's `validators.go`, response shapes in `serializers.go` and GORM queries in `models.go`. It lists every new branch the diff adds, such as a new query filter in `ArticleList` or a new rule in a validator, and searches the package's tests for one that reaches it.

Then it reads the package's own test file, because this repository keeps all of a package's tests in one `unit_test.go` inside the package, not in a separate `_test` package. `users/unit_test.go` has a `TestMain` that calls `common.TestDBInit()`, runs `AutoMigrate()` and frees the database with `common.TestDBFree` at the end. `articles/unit_test.go` builds a Gin engine with `setupRouter()`, wires `users.AuthMiddleware`, creates fixtures with `createTestUser` and `createArticleWithUser`, and resets data with `resetDBWithMock`. Requests go through `httptest` with `common.HeaderTokenMock` adding a JWT, and assertions use `testify`'s `assert`. The repository's `AGENTS.md` says the same: tests live beside the source, use `TestDBInit` and `TestDBFree`, and `go test ./...` runs before every commit.

The agent adds new `TestXxx` functions to those files in the same style, one per behavior, named for what they check (`TestArticleListFilterByFavorited` rather than `TestArticleList2`). Where the existing file already drives a test from a table of cases, as `TestArticleRouters` does with `articleRequestTests` and `TestWithoutAuth` with `unauthRequestTests`, it adds rows to that table instead of a new function. It reuses the fixture helpers rather than inserting rows by hand, so a later change to a model only needs one helper updated.

It then runs `go test ./...` with the module's `go 1.21` directive and reads the output, and runs `gofmt -l` the way `scripts/gofmt.sh` does in CI, because a test file that fails formatting turns the check red too. The SQLite driver, `gorm.io/driver/sqlite` over `mattn/go-sqlite3`, compiles C code, so the sandbox needs cgo and a C compiler, which the agent checks before the first run. A test that fails because a handler is wrong stays out of the commit; the prompt forbids changing application code, and the agent reports it instead.

## Start it with one API call

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

```json
{
  "agent": "codex",
  "input": "This pull request changes the users and articles packages. Read the diff against the base branch and list the new or changed behavior in users/ and articles/ (routers, validators, serializers, models) that no existing test covers. Write Go tests for each item in that package's unit_test.go, following the existing style: TestMain with common.TestDBInit and TestDBFree, setupRouter, the createTestUser and createArticleWithUser helpers, httptest requests with common.HeaderTokenMock, testify assert, and table cases where the file already uses them. Run go test ./... and gofmt -l until the new tests pass and are formatted. Do not change application code: if a test fails because the code is wrong, leave it out and describe the failure. Push the tests to this pull request's branch and post a comment listing each test and the behavior it covers.",
  "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 a commit on the pull request's branch that changes only `users/unit_test.go`, `articles/unit_test.go` or `common/unit_test.go`. Each new test is a `func TestXxx(t *testing.T)` that sets up its rows with the existing helpers, sends a request through the Gin router with `httptest.NewRecorder`, and asserts the status code and the decoded JSON, or calls a model or validator function directly when that is what the pull request changed. No dependency is added to `go.mod`.

The agent's comment on the pull request lists every test by name, the behavior it pins and the commands it ran, `go test ./...` and `gofmt -l`. If a test caught a bug, for example a handler returning `200` for an article the user does not own, the comment shows the failing assertion and the line in `routers.go`. The repository's CI then runs `scripts/coverage.sh` across Go 1.21, 1.22 and 1.23 on the pushed commit, which is the check a reviewer trusts.

Before merging, a reviewer checks that each test leaves the shared SQLite test database in a state the next test expects, since the package's tests share one database for the whole `TestMain`, and that tests do not rely on the order rows come back in when the handler does not sort them.

## Run it on every pull request opened

On the Project connected to the repository, add an automation with this prompt and a GitHub trigger on `pull_request.opened`. Each pull request from someone who can write to the repository starts its own run; the run's prompt names the pull request number, URL, title, author and base branch. Pull requests from forks never fire it. To also cover commits pushed after the pull request opens, add `pull_request.synchronize` to the same trigger.

A service that already receives GitHub webhooks can start the same runs itself: on a `pull_request` event with action `opened`, send this request with the pull request's URL in `resources`, which clones its branch. Read the result from the session, or set a webhook URL on the API key and get `run.completed` when the turn ends.

## Why run it on Rush?

Go tests for a router are fast to run but slow to get right: the first versions fail on a missing auth header, a wrong JSON key or a row left over from the previous test, and each fix is another `go test` cycle on someone else's branch. On Rush that loop happens in the agent's own sandbox at the pull request's branch, with its own module cache and its own SQLite file, while your checkout and your Go build cache stay as they were.

The run ends with evidence, not a summary. The trajectory has every `go test` invocation and its output, and the repository's CI repeats the suite on three Go versions for the pushed commit. Because each pull request gets its own run, five open pull requests get their tests in parallel rather than in a queue on one machine.

## Frequently asked questions

### Will it put the tests in a separate _test package?

Not in this repository. Each package keeps its tests in `unit_test.go` in the same package, so they can call unexported helpers like `resetDBWithMock`. The agent follows whatever the package already does; in a codebase that uses external `_test` packages, it uses those.

### Does it use testify or the standard library only?

It uses what the module already imports. This one requires `github.com/stretchr/testify` and its tests use `assert`, so the new tests do too. In a module that uses only `testing`, the agent writes `t.Errorf` checks and adds no dependency.

### Does it need a database server?

No. `common.TestDBInit()` opens a SQLite file for the tests, so `go test ./...` runs without Postgres or MySQL. The SQLite driver needs cgo, which the agent checks for before it starts.

### How does it avoid flaky tests?

It runs the package's tests more than once before pushing, does not depend on map iteration order or wall-clock time, and creates the rows each test reads rather than relying on rows another test left behind.
