# Write Tests for a Go CLI

Yes. An agent writes Go tests for your CLI's flags, config loading and input sources, adds golden-file output tests, runs go test ./... and opens a pull request.

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

## What the agent does

The agent clones the repository into its own cloud sandbox and reads the command before the tests. In a Cobra and Bubble Tea CLI like charmbracelet/glow (module `charm.land/glow/v3`, Go 1.26.7, Cobra 1.10.2, Bubble Tea v2, Glamour v2), `main.go` holds the root command and its flags as package-level variables: `--pager`, `--tui`, `--style` (default `auto`), `--width`, `--all`, `--line-numbers`, `--preserve-new-lines`, a hidden `--mouse`, and a persistent `--config`. `validateOptions` runs as `PersistentPreRunE` and decides the style and width when they were not set, and `tryLoadConfigFromDefaultPlaces` reads `glow.yml` through Viper from `GLOW_CONFIG_HOME`, then `XDG_CONFIG_HOME/glow`, then the user config directory, and writes a default file when none is found.

Then it reads what is already tested, so it adds rather than repeats. `glow_test.go` has one table test that parses `-p`, `-s light` and `-w 40`. `url_test.go` checks GitHub and GitLab README URL resolution, but every case calls `t.Skip("test uses network, sometimes fails for no reason")`, so none of them runs. `ui/` and `utils/` have their own tests for the pager, raw painting and text sizing. Nothing tests `sourceFromArg`, the function that turns an argument into something to read: `-` for stdin, a GitHub or GitLab shorthand, an `http` or `https` URL (any other scheme is refused as not supported, and a non-200 answer is an error), a directory searched for a README by the names in `readmeNames`, or a plain file.

It writes tests for each of those without the network. URL sources run against an `httptest.Server` that answers 200, 404 and a redirect, so the skipped tests' intent finally runs. Directory sources use `t.TempDir()` with a `Readme.md` nested a level down and with none at all, which must return `missing markdown source`. Config loading sets `GLOW_CONFIG_HOME` with `t.Setenv` to a temporary directory, so the default `glow.yml` it writes never lands in a real home directory. Because the flags are package globals that `rootCmd.ParseFlags` mutates, each test resets them, and none of these tests calls `t.Parallel()`.

For output it adds golden files. `executeCLI` renders to any `io.Writer`, so a test feeds a fixed Markdown file through it with a fixed style such as `notty` and a fixed `--width`, and compares the bytes with a file under `testdata/`, using `charmbracelet/x/exp/golden`, which is already in `go.sum`, and an `-update` flag to rewrite them. Where the TUI is involved it uses `teatest` to drive the Bubble Tea model, send key presses and compare the final screen with a golden file. It runs `go test ./...` and then `go test -race ./...`, the same `-race` flag the repository's `coverage.yml` workflow uses.

## Start it with one API call

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

```json
{
  "agent": "claude",
  "input": "Write Go tests for this CLI. Read main.go and the config code first, and list what is untested: flag parsing and option validation, config file loading from each directory it searches, and every input source the CLI accepts (stdin, repository shorthands, http and https URLs, directories searched for a README, plain files, and the errors for each). Test URL sources against an httptest server, never the real network, and keep every file the code writes inside t.TempDir() by setting the config environment variables with t.Setenv. Reset any package-level flag variables between tests. Add golden-file tests for rendered output with a fixed style and width, with an -update flag to regenerate them, and use teatest with golden files where the TUI is involved. Run go test ./... and go test -race ./... until both pass, and open a pull request that lists what each new test covers.",
  "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 CLI like glow, the diff adds test files beside the code they test (`main_test.go` or a `source_test.go` for `sourceFromArg`, a config test, a `ui/` test for the TUI), a `testdata/` folder with the Markdown input and the golden output, and test-only dependencies in `go.mod` and `go.sum` such as `teatest`. The description lists each test against the path it covers and calls out the network-dependent URL tests that were skipped and are now covered by a local server.

The session transcript keeps every command with its output: `go test ./...` and `go test -race ./...`, including runs that failed while the tests were being written, and the run that generated the golden files. The pull request's checks then run the repository's own workflows, which for glow include the `coverage` job's `go test -race -covermode atomic ./...`.

Before merging, a reviewer reads the golden files, since they are the expected output and a wrong one would lock in a rendering bug, and checks that nothing in the tests reads the real home directory or the network.

## Run it on every pull request that adds a flag

When a pull request adds a flag or an input source without a test, send `POST /o/{org}/sessions` with the pull request's URL, `https://github.com/<owner>/<repo>/pull/<n>`, in `resources`; the agent clones that branch and adds the tests for what changed. A small step in your own CI or a GitHub webhook handler on your server can send it.

Set a `webhook_url` on the API key and Rush posts `run.completed` or `run.failed` when the turn ends, with a `session_url` for the result. A follow-up message to `POST /o/{org}/sessions/{id}/messages` asks for more cases in the same sandbox.

## Why run it on Rush?

CLI tests that touch config files, environment variables and a home directory are exactly the ones that are unsafe to iterate on a laptop: one test that forgets `t.TempDir()` writes a `glow.yml` into a real config directory. In a Rush sandbox the agent writes and reruns them on a machine that exists for this run, and the `-race` runs that slow a laptop do not slow anyone.

The transcript keeps every `go test` run with its output, so the reviewer sees the failures that shaped the tests and the passing `-race` run, and the golden files are generated from a run that is on the record rather than from someone's terminal.

## Frequently asked questions

### Will the tests hit the network?

No. The prompt says to test URL sources against `httptest.Server`. glow's own URL tests are skipped because they used the network and failed at random; the new ones cover the same code paths against a local server that answers 200, 404 and other statuses on demand.

### How do golden files get updated when the output changes on purpose?

Each golden test takes an `-update` flag, so `go test ./... -update` rewrites the files and the diff shows exactly what changed in the output. The pull request explains the flag so the team knows how to use it.

### Can teatest test a Bubble Tea v2 program?

The agent checks which `teatest` release matches the Bubble Tea version in `go.mod` before adding it. If no compatible release exists, it tests the model's `Update` and `View` functions directly with golden files and says so in the pull request.

### Will it refactor main.go to make it testable?

Only as far as tests need and no further. glow's `executeCLI` already writes to an `io.Writer`, so output can be tested as is. If a function reads `os.Stdin` directly, the agent may pass the reader in, and the pull request lists every non-test change.
