All jobs

Write Unit Tests for an Elixir Project

Yes. An agent runs mix test --cover on the Elixir project, picks the modules the report leaves thin, writes ExUnit tests with the project's own cases and opens a PR.

Run this on RushIn a Project that has your repository.

What the agent does

The agent starts by finding out where the suite is thin, which in a project like hexpm/hexpm is a real question: test/ mirrors lib/ closely, with DataCase, ConnCase and a ConcurrencyCase in test/support/, a factory, StreamData properties and Mox mocks for billing, geolocation, HTTP, the CDN, the store and SSO. mix.exs requires Elixir ~> 1.20, compiles test/support only in the test env, and defines a test alias that runs ecto.create --quiet, ecto.load --skip-if-loaded and ecto.migrate before test. There is no test_coverage configuration, so the agent runs mix test --cover --export-coverage default followed by mix test.coverage, which prints coverage per module, and sorts that list rather than guessing from file names.

It then compares the per-module numbers with the modules that have no test file of their own. In this repository those include Hexpm.VersionRequirement in lib/hexpm/ecto/version_requirement.ex, an Ecto type whose cast, load, dump and encode clauses are reached only through schemas; Hexpm.Accounts.TFA, whose generate_secret, time_based_token and token_valid? are exercised through controller tests in test/hexpm_web/controllers/tfa_auth_controller_test.exs; Hexpm.BlockAddress, whose blocked?, allowed? and try_reload are covered through the attack plug test; Hexpm.Accounts.ReservedUsername.by_name; and Hexpm.Emails.OutboxReconciler, an Oban worker whose perform discards, purges and requeues outbox entries and is touched only by outbox_worker_test.exs. Indirect coverage leaves branches out, and the coverage listing shows which.

The agent writes direct tests for the five modules with the lowest numbers that hold logic. For VersionRequirement it is a plain ExUnit.Case with async: true: each cast clause, a bad string, load and dump round-trips and embed_as. For TFA it checks that token_valid? accepts the token time_based_token produces from a generate_secret and rejects a neighbouring one. For BlockAddress it uses DataCase, inserts entries through the factory, calls reload and asserts blocked? and allowed? on an IP inside and outside a mask. For OutboxReconciler it uses DataCase with Oban in testing: :manual from config/test.exs, inserts outbox rows in each state the worker handles, runs perform/1 and asserts what was discarded, purged and requeued. Mocks come from test/support/mocks.ex with Mox expectations, never by stubbing the module under test.

It runs the suite the way .github/workflows/main.yml does: Elixir 1.20.4 on OTP 29.1 against a postgres:18 service with MIX_ENV=test, mix deps.get, mix compile and mix test, plus mix format --check-formatted from the format job and mix compile --warnings-as-errors, since a new test module with an unused alias would fail that job. config/test.exs already points the repo at hexpm_test with the SQL sandbox, uses in-memory stores for every bucket and the Swoosh test mailer, so nothing external is needed. It records mix test.coverage before and after and puts both module listings in the pull request.

Start it with one API call

POST https://api.prix.dev/o/{org}/sessions with this body:

{
  "agent": "claude",
  "input": "Run the test suite of this Elixir project with coverage: mix test --cover --export-coverage default, then mix test.coverage, against a Postgres the way .github/workflows/main.yml does with MIX_ENV=test. Cross the per-module listing with the modules under lib/ that have no test file of their own, and pick the five least-covered that hold real logic. Write ExUnit tests for them using the project's own cases from test/support (DataCase, ConnCase), the factory and the Mox mocks in test/support/mocks.ex; test behaviour through public functions, not implementation details, and never stub the module under test. Do not change production code unless a test reveals a bug; if it does, fix it in a separate commit and say so. Run mix test, mix format --check-formatted and mix compile --warnings-as-errors, and open a pull request with the per-module coverage before and after.",
  "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

A pull request on its own branch that adds test files beside the modules it chose, such as test/hexpm/ecto/version_requirement_test.exs, test/hexpm/accounts/tfa_test.exs, test/hexpm/block_address_test.exs and test/hexpm/emails/outbox_reconciler_test.exs, and changes nothing under lib/ unless a test found a bug. The description pastes the mix test.coverage lines for those modules before and after, says why each was chosen over others with lower numbers, and shows mix test, mix format --check-formatted and the warnings-as-errors compile passing.

If a test uncovered a defect, such as a cast clause accepting a requirement string that Version.parse_requirement rejects, the fix is its own commit and the description explains it. The session transcript keeps every mix command with its output, including both coverage listings, and GET /o/{org}/sessions/{id}/messages returns it.

A reviewer checks that tests marked async: true touch no database, that Oban tests use the manual testing mode rather than starting queues, and that Mox expectations are set with expect for calls the code must make and stub only for ones it may make.

Run it on every nightly

Create an automation in the Project with a schedule trigger, a cron such as 0 2 * * * with your timezone, and a prompt asking for tests on the least-covered module changed in the last day. Each night opens one pull request on its own branch with its coverage listing, so the suite follows the code instead of catching up once a quarter.

Set a webhook URL on the API key and Rush POSTs run.completed when the turn finishes, with the session_url and the result, so a handler can post the pull request link and the module numbers where the team will read them in the morning.

The use case this job follows, with its automation and trigger: Write the missing tests.

Why run it on Rush?

In a large Phoenix application the untested code is not obvious: most modules are reached by some test, and only a per-module coverage listing shows which branches no test asserts. The agent produces that listing in its own sandbox, with the Postgres version and Elixir and OTP pair the workflow pins, and writes the tests without a single compile on your machine.

The pull request carries the before-and-after listings, the format and warnings checks and a transcript of every command, so the delta is a fact to check rather than a claim to trust. Several Elixir services can each get a run at once, and a person still merges each one.

Frequently asked questions

The suite already has hundreds of test files. What is left?

Modules covered only through controllers or plugs: the VersionRequirement Ecto type, TFA, BlockAddress, ReservedUsername and the OutboxReconciler worker have no test file of their own in this repository, and mix test.coverage shows the branches those indirect tests miss. The agent chooses from that list, not from file names.

Does it need Postgres, S3 and the other services?

Only Postgres. config/test.exs replaces every bucket with Hexpm.Store.Memory, uses the Swoosh test mailer, puts Oban in manual testing mode and routes billing, geo, HTTP and CDN through Mox mocks, which is what lets the suite run in CI with a single postgres:18 service.

Will it use Mox to stub the module it is testing?

No. Mocks are for the behaviours in test/support/mocks.ex that the module calls, with expect for required calls. Stubbing the module under test would test nothing, and the prompt forbids it.

Which Elixir and OTP does it run?

Elixir 1.20.4 on OTP 29.1, the pair in main.yml, so a test that passes in the sandbox passes in the Test Hexpm job. mix.exs requires Elixir ~> 1.20, and the agent says in the pull request which versions it used.