# Add Missing Tests to Every Pull Request in a Laravel App

Yes. An agent reads each pull request to your Laravel app, writes PHPUnit tests for the song and playlist code it changes, runs artisan test and pushes them.

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

## What the agent does

The agent reads the pull request's diff and lists what changed behind each endpoint. In a Laravel app like koel the API controllers live in `app/Http/Controllers/API/`: `SongController.php`, `PlaylistController.php`, `PlaylistSongController.php`, `PlaylistFolderController.php` and `MovePlaylistSongsController.php` among them, with routes in `routes/` and the real work in services such as `App\Services\Playlist\PlaylistFolderService`. For each changed controller action, form request or service method, it works out the HTTP call and the user that would reach the new code and checks the existing tests for one that makes it.

Then it reads how koel tests those features. `phpunit.xml.dist` defines three suites, `feature`, `unit` and `integration`, under `tests/`, and runs them on an in-memory SQLite database (`DB_CONNECTION=sqlite`, `DB_DATABASE=:memory:`) with array caches, a sync queue and empty AI and Spotify keys. Feature tests such as `tests/Feature/PlaylistTest.php`, `PlaylistSongTest.php`, `SongTest.php` and `SongVisibilityTest.php` extend `Tests\TestCase`, mark methods with PHPUnit's `#[Test]` attribute, create users and playlists with helpers from `tests/Helpers.php` (`create_user`, `create_playlist`, `create_playlists`) and model factories like `Song::factory()->createMany(2)`, and call the API with `getAs` and `postAs` as a given user.

The agent writes the new tests into those files in that style, as feature tests because koel's own `AGENTS.md` says most tests should be feature tests and that every change must be programmatically tested. Each test covers one path, including the failure and edge paths the same file asks for: a playlist owner can reorder songs, a collaborator who is not the owner cannot delete the playlist, a private song stays hidden from another user. It asserts the JSON shape with the resource's `JSON_STRUCTURE` constant, as `PlaylistTest` does with `PlaylistResource::JSON_STRUCTURE`, and the database state with `assertDatabaseHas` where the action writes rows.

It runs the affected files first with `php artisan test --compact tests/Feature/PlaylistTest.php`, as koel's `AGENTS.md` recommends, then the whole `feature` suite, on the PHP version `composer.json` requires (`>=8.3`) with `laravel/framework` `^13.0`. When a new test fails because the controller is wrong, the agent does not change the controller, because the prompt forbids application changes; it leaves the test out and describes the failing request in its comment.

## Start it with one API call

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

```json
{
  "agent": "claude",
  "input": "This pull request changes the song and playlist controllers. Read the diff against the base branch and list the new or changed behavior in app/Http/Controllers/API (the song and playlist controllers), their form requests and the services they call that no existing test covers. Write PHPUnit feature tests for each item in the matching file under tests/Feature (PlaylistTest.php, PlaylistSongTest.php, SongTest.php and so on), following the existing style: #[Test] methods, create_user and create_playlist from tests/Helpers.php, model factories, getAs and postAs, and the resource JSON_STRUCTURE constants. Cover failure paths as well as the happy path. Run php artisan test --compact on the files you changed, then the feature suite, until the new tests pass. 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 with new `#[Test]` methods in the existing files under `tests/Feature/`, and a new test class only when the pull request added a controller with no test file yet. Every test creates its own user and data through the helpers and factories, calls the endpoint as that user, and asserts the status, the JSON structure and the rows the action wrote. Nothing under `app/`, `routes/` or `database/` changes.

The agent's comment on the pull request lists each test method, the behavior it covers and the `php artisan test` commands it ran. When a test exposed a defect, such as a playlist endpoint that lets a non-owner change it, the comment quotes the failing assertion and names the controller method instead of pushing a red test. Your CI runs the suites again on the pushed commit, and that run is the result a reviewer relies on.

Before merging, a reviewer checks that authorization tests use the right kind of user for what they claim (owner, collaborator, admin or a stranger), that tests involving time or ULIDs freeze them as `PlaylistTest` does with `Ulid::freeze()`, and that no test depends on media files outside `tests/songs/`.

## Run it on every pull request opened

Give the Project bound to your Laravel repository an automation with this prompt and a GitHub trigger on `pull_request.opened`, filtered with `branch_patterns` to the branches you merge into. Each pull request opened by someone with write access then gets a run of its own; the prompt it receives includes the pull request number, URL, title, author and base branch. Pull requests from forks never fire it, and neither do the ones the Rush GitHub App opens itself.

If you would rather choose which pull requests get tests, handle GitHub's `pull_request` webhook in your own app and send this request with the pull request's URL in `resources` only when the diff touches `app/Http/Controllers/API`. The delivery id as `Idempotency-Key` keeps a retried webhook from starting a second run.

## Why run it on Rush?

Writing feature tests for another developer's branch means switching your local koel install to that branch, reinstalling Composer and pnpm dependencies, and rerunning the suite while your own work waits. On Rush every pull request gets its own sandbox with a clone at its branch and PHP ready to run `php artisan test`, so nothing in your local environment changes and several pull requests are covered at once.

What comes back can be verified line by line. The trajectory keeps each `artisan test` command with its output, so a reviewer can see a test fail for a wrong assumption and pass after the fix, and the repository's own CI runs the pushed tests again. The tests sit in the pull request next to the change they describe, where the reviewer is already reading.

## Frequently asked questions

### Will it write Pest tests?

Not in koel, which uses PHPUnit classes and says in its `AGENTS.md` to convert any Pest test to PHPUnit. The agent follows the framework the repository uses; in an app whose tests are Pest files, it writes Pest tests with the same helpers.

### Does it need MySQL to run the tests?

No. koel's `phpunit.xml.dist` runs every suite on an in-memory SQLite database, so `php artisan test` needs no database server. If your app's tests use MySQL or Postgres, the agent starts that database in its sandbox and runs your migrations first.

### Does it test the Vue frontend too?

The prompt keeps the run on the PHP controllers and `tests/Feature/`. koel's frontend has its own test setup; ask for those tests in a separate run when a pull request changes the client.

### How many tests does it add per pull request?

As many behaviors as the diff adds or changes and no more. A small fix might get one test for the bug and one for the path next to it; a new endpoint gets its happy path, its authorization failures and its validation errors.
