All jobs

Write pytest Tests for a FastAPI App

Yes. An agent runs the FastAPI app's suite with coverage, writes tests for the untested summarizer, CRUD layer and config against a real Postgres, and opens a PR.

Run this on RushIn a Project that has your repository.

What the agent does

The agent starts by measuring, because this app is not untested. In testdrivenio/fastapi-tdd-docker the project/ directory holds a FastAPI 0.115 app with Tortoise ORM 0.25, asyncpg, aerich migrations and newspaper3k; requirements-dev.txt adds pytest 8.3, pytest-cov, pytest-xdist, black, flake8 and isort, and .coveragerc turns on branch coverage and omits tests/. tests/conftest.py builds the app with create_application(), overrides get_settings with DATABASE_TEST_URL, and offers two module-scoped fixtures: test_app with no database and test_app_with_db, which calls register_tortoise with generate_schemas=True. tests/test_summaries.py already covers the five /summaries/ routes and their 404 and 422 cases through the database, and tests/test_summaries_unit.py covers the same routes with monkeypatch on crud. The agent runs python -m pytest --cov=app --cov-branch --cov-report=term-missing first and reads which lines are left.

What that run leaves out is the code the route tests mock away. app/summarizer.py builds a newspaper.Article, downloads and parses it, finds or downloads the punkt_tab tokenizer, runs article.nlp() and writes article.summary back with TextSummary.filter(id=...).update(...); every route test replaces generate_summary with a stub, so none of that runs. app/api/crud.py is reached only through routes, so put returning None when no row matched and delete on a missing id are asserted only indirectly. app/db.py has init_db and a generate_schema coroutine that initialises Tortoise and generates schemas, and app/config.py has get_settings with an lru_cache, neither of which the tests call. app/main.py wires init_db(app) at import time, which the test_app fixture avoids by calling create_application() instead.

The agent writes those tests in the project's style. tests/test_summarizer.py patches newspaper.Article with a small fake whose download, parse and nlp record calls and set summary, points nltk.data.find at a result so nothing is downloaded, and asserts through the test_app_with_db fixture that the row's summary column changed; a second case covers the LookupError branch where nltk.download is called. tests/test_crud.py calls crud.post, get, get_all, put and delete directly inside the test_app_with_db lifespan with anyio, asserting the None and count returns on missing ids. tests/test_config.py checks get_settings reads ENVIRONMENT, TESTING and DATABASE_URL from the environment and that the cache returns the same object; tests/test_db.py checks generate_schema against the test database URL.

It runs the suite the way CI does. The workflow in .github/workflows/main.yml builds project/Dockerfile.prod, starts the container with DATABASE_URL and DATABASE_TEST_URL set to sqlite://sqlite.db, and runs python -m pytest ., then flake8 ., black . --check and isort . --check-only; docker-compose.yml runs against the web_test Postgres database that db/create.sql creates. The agent runs the suite both ways, with Postgres from the compose file and with the SQLite URL the workflow uses, and runs the three formatters on the new files. Then it records coverage again and puts both tables in the pull request.

Start it with one API call

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

{
  "agent": "claude",
  "input": "Write pytest tests for the untested code in this FastAPI app under project/. Run python -m pytest --cov=app --cov-branch --cov-report=term-missing first and keep the table. Using the existing fixtures in tests/conftest.py, add tests for app/summarizer.py with a patched newspaper.Article and a patched nltk.data.find, including the LookupError branch; for app/api/crud.py called directly inside the test_app_with_db lifespan, including the None return on a missing id; for app/config.py get_settings and its cache; and for app/db.py generate_schema against DATABASE_TEST_URL. Use the real test database the way tests/test_summaries.py does, not mocks of the ORM. Do not change production code unless a test reveals a bug; if it does, say so in the description. Run the suite against the compose Postgres and against the sqlite://sqlite.db URL the workflow uses, run flake8, black --check and isort --check-only on the new files, and open a pull request with the 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 tests/test_summarizer.py, tests/test_crud.py, tests/test_config.py and tests/test_db.py under project/ and touches nothing in app/. The description pastes the term-missing table before and after, names the lines in summarizer.py, crud.py, config.py and db.py the new tests reach, and shows the suite passing against both database URLs with the three formatter checks clean.

If a test turned up a real defect, such as put succeeding on a row that get then cannot find, the description says so and the fix is a separate commit on the same branch, so the reviewer can accept the tests and discuss the fix. The session transcript keeps every pytest, docker compose and formatter command with its output, and GET /o/{org}/sessions/{id}/messages returns it.

A reviewer checks that the summarizer tests never reach the network, that the crud tests run inside the Tortoise lifespan rather than against an unregistered model, and that none of the new tests depend on a row left behind by test_summaries.py in the shared module-scoped database.

Run it on every night after a merge to main

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 run opens one pull request on its own branch, so coverage grows with the code instead of being a quarterly project.

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 coverage line where the team will see it in the morning.

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

Why run it on Rush?

This app's remaining gaps are exactly the ones nobody writes tests for by hand: a background task that calls a third-party library and downloads an NLTK model, a schema generator, a cached settings object. The agent isolates each of those in its own sandbox, with Postgres from the compose file and the SQLite URL from the workflow, and runs the suite both ways without touching your machine.

The pull request comes with the before-and-after coverage tables, the formatter checks and a transcript of every command, so the delta is verifiable rather than claimed. Several services can each get the same run at once, and a person merges each one.

Frequently asked questions

The route tests already exist. What is left to test?

app/summarizer.py is mocked out of every route test, app/api/crud.py is reached only through routes, and app/db.py and app/config.py are not called by the suite at all. The agent's first coverage run shows those lines, and the new tests target them specifically.

Does the summarizer test download anything?

No. newspaper.Article is replaced with a fake that records download, parse and nlp and sets summary, and nltk.data.find is patched so punkt_tab is neither looked up on disk nor downloaded. One case forces the LookupError branch to check that nltk.download would be called.

Postgres or SQLite?

Both. docker-compose.yml runs the suite against the web_test Postgres database, and the workflow runs it with sqlite://sqlite.db, so the agent runs the new tests under each URL and reports both results.

Will it reformat existing files?

No. It runs flake8, black --check and isort --check-only on the files it adds, matching the checks in Dockerfile.prod and the workflow, and leaves app/ and the existing tests as they are.