What the agent does
The agent reads how the app already tests itself before adding anything. In a Spring Boot app like spring-projects/spring-petclinic it finds spring-boot-starter-parent 4.1.0 and Java 17 in pom.xml, both Maven (./mvnw) and Gradle (build.gradle) builds, and test dependencies on spring-boot-testcontainers, testcontainers-junit-jupiter and testcontainers-mysql. The controller tests, OwnerControllerTests, PetControllerTests and VisitControllerTests, are @WebMvcTest slices with the repository replaced by @MockitoBean, so none of them reach a database. MySqlIntegrationTests does boot the app against a MySQLContainer for mysql:9.7 with @ServiceConnection, but it holds two tests: findAll on the vet repository and a GET /owners/1 that checks for 200. PostgresIntegrationTests uses Spring Boot's Docker Compose support with the postgres service in docker-compose.yml, not Testcontainers.
So the gap is the full owner and pet flow against each real database. The agent adds testcontainers-postgresql beside testcontainers-mysql in both pom.xml and build.gradle, so the two builds stay in step. It writes one abstract test class with the HTTP cases and two subclasses, one with @ActiveProfiles("mysql") and a MySQLContainer, one with @ActiveProfiles("postgres") and a PostgreSQLContainer for postgres:18.4, the image docker-compose.yml pins. Each container is a static @Container field with @ServiceConnection, so Spring Boot wires the datasource from it without the MYSQL_URL or POSTGRES_URL properties that application-mysql.properties and application-postgres.properties read, and spring.sql.init.mode=always loads db/mysql or db/postgres schema.sql and data.sql into the fresh database.
The tests drive the app the way a browser does, through @SpringBootTest(webEnvironment = RANDOM_PORT) and a RestTemplate, as the existing integration tests do. They post the /owners/new form and follow the redirect to the new owner's page, search GET /owners?lastName= and check the page of five that OwnerController builds with PageRequest, post /owners/{ownerId}/edit, add a pet with /owners/{ownerId}/pets/new and edit it, and add a visit with /owners/{ownerId}/pets/{petId}/visits/new. They also send what the validation should refuse: a blank last name, a telephone that fails the \d{10} pattern on Owner, and a pet with a duplicate name or a birth date in the future, which PetValidator and PetController reject. PetClinic has no delete endpoint for owners or pets, so the agent says so instead of inventing one.
Testcontainers needs a Docker daemon. The agent checks for one in its sandbox, where it can install packages with sudo, and runs ./mvnw verify, which also runs the javaformat and checkstyle plugins the build declares. If Docker cannot run there, the classes skip themselves through @Testcontainers(disabledWithoutDocker = true), exactly as MySqlIntegrationTests does, and the agent says in the pull request that they were skipped in the sandbox rather than reporting them as passed.
Start it with one API call
POST https://api.prix.dev/o/{org}/sessions with this body:
{
"agent": "claude",
"input": "Add Testcontainers integration tests that run this Spring Boot app against real MySQL and PostgreSQL containers, covering owner and pet CRUD through the HTTP layer. Read pom.xml, build.gradle, the application properties for each database profile and the existing integration tests first, and reuse their style: @SpringBootTest with a random port, @ServiceConnection containers, the same image tags the repository already pins. Put the HTTP cases in one shared class run once per database. Cover creating, finding, paging and editing owners, adding and editing pets and visits, and the validation errors the forms return. Add any Testcontainers module to both the Maven and Gradle builds. Do not change application code. Run ./mvnw verify; if Docker is not available, say that the container tests were skipped, never that they passed. Open a pull request.",
"resources": [
{
"url": "https://github.com/your-org/your-app"
}
]
}- 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.
- 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.
- 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
The pull request adds the shared test class and its MySQL and PostgreSQL subclasses under src/test/java/org/springframework/samples/petclinic/, and the testcontainers-postgresql dependency in pom.xml and build.gradle. It changes nothing under src/main. The existing MySqlIntegrationTests and PostgresIntegrationTests stay as they are; the description notes that the new classes cover what they check and leaves removing them to the maintainers.
The checks are the evidence. In spring-petclinic the maven-build.yml and gradle-build.yml workflows run ./mvnw -B verify and the Gradle build on the pull request, on GitHub runners that have Docker, so both container suites run there. The session keeps the agent's own ./mvnw verify output from the sandbox, with each test class and whether it ran or was skipped, and the description lists every case by route.
A reviewer checks that the tests do not depend on each other's rows, since both databases start from the same data.sql seed and a test that adds an owner must not break a later count, that the container images match what production runs, and that the extra build time of starting two databases is acceptable in CI.
Run it on every database schema change
When a pull request changes schema.sql, a migration or an entity, have a GitHub pull_request webhook send this request with the pull request URL in resources, and add to the prompt that the tests must cover the changed tables. The agent works on that branch, so a change that runs on H2 but fails on MySQL or PostgreSQL is caught before merge, in its own session and sandbox.
With a webhook URL on the API key, Rush POSTs run.completed or run.failed when the turn ends, with the session URL, and your server can link the test pull request from the schema change.
The use case this job follows, with its automation and trigger: Write the missing tests.
Why run it on Rush?
Running a Spring Boot app against two real databases on a laptop means Docker Desktop, two containers pulled and started, a JVM build, and a working tree tied up until it finishes. On Rush the agent builds with the repository's own Maven wrapper in its own sandbox, and every ./mvnw verify it ran is kept with its output, including whether the container tests actually ran there or skipped.
That distinction matters for this job. A Testcontainers suite that silently skips looks green, and the pull request says plainly which classes ran where: in the sandbox, on the pull request's own workflows, or both. The same request can run across several Spring services at once, each in its own sandbox, each with a pull request someone reviews.
Frequently asked questions
Why not just use H2 for integration tests?
H2 accepts SQL that MySQL and PostgreSQL reject and the other way round, so a test that passes on H2 says little about production. PetClinic keeps separate schema.sql files under db/h2, db/mysql and db/postgres for exactly this reason, and the new tests load the real ones into the real engines.
Does it work with Gradle as well as Maven?
Yes. The agent reads which build files the repository has and adds the Testcontainers modules to each, so ./mvnw verify and the Gradle build run the same tests. In spring-petclinic both builds are maintained, and both get the dependency.
What does @ServiceConnection do here?
It tells Spring Boot to read the datasource URL, user and password from the running container instead of from properties. The test needs no spring.datasource.url override and no @DynamicPropertySource method, and the app connects to whatever port the container was given.
Will it replace my @WebMvcTest tests?
No. Slice tests with mocked repositories are fast and check controller logic, and they stay. The new tests sit beside them and check what the slices cannot: that the queries, the schema and the forms work together on a real database.