db-snapshot-restore

Action-taking agent that gives integration tests a clean database between cases - captures a baseline snapshot once (template DB for Postgres, mysqldump for MySQL, JSON dump for Mongo), then restores the test DB from that baseline before each test (or each suite), tearing down all open sessions first so the rebuild succeeds. Also wires the per-test BEGIN ... ROLLBACK shortcut when the ORM cooperates and the schema doesn't change.

Install with Claude Code
/plugin install qa-test-environment@testland-qa

Agents run in Claude Code. This installs the qa-test-environment plugin. First time? Add the marketplace with /plugin marketplace add testland/qa

View source (opens in a new tab)
Agent
Category
Foundations
Version
v1.4.5
Tools
Read, Write, Edit, Grep, Glob, Bash(psql *), Bash(createdb *), Bash(dropdb *), Bash(pg_dump *), Bash(pg_restore *), Bash(mysql *), Bash(mysqldump *), Bash(mongoimport *), Bash(mongodump *), Bash(docker compose *), Bash(docker exec *)

A maintenance agent that turns "the previous test left junk in the DB" into a deterministic snapshot/restore loop, using the engine's native template/dump primitives. [pg-tpl]: https://www.postgresql.org/docs/current/manage-ag-templatedbs.html [pg-begin]: https://www.postgresql.org/docs/current/sql-begin.html [pg-dump]: https://www.postgresql.org/docs/current/app-pgdump.html

When invoked

ModeTriggerAction
snapshotFirst-time setup OR schema/seed-data changedCapture baseline (template DB / mysqldump / mongodump).
restoreBefore each test/suite - DB is dirtyDrop + recreate the test DB from the baseline.
wrapPer-test isolation needed; schema stable; ORM honors transactionsOpen BEGIN; run test; ROLLBACK.
list"What baselines do I have?"Read-only enumeration.

Mode 1 - Postgres template DB (snapshot + restore)

CREATE DATABASE ... TEMPLATE is the engine's native fast clone ([pg-tpl][pg-tpl]); requires no other sessions on the source.

# Snapshot: build template DB once
createdb -T template0 ${APP}_template
psql -d ${APP}_template -f schema.sql -f seed.sql
psql -d postgres -c "UPDATE pg_database SET datistemplate=true,
  datallowconn=false WHERE datname='${APP}_template';"

# Restore (invoke from beforeAll / session fixture):
psql -d postgres -v ON_ERROR_STOP=1 -c "
  SELECT pg_terminate_backend(pid) FROM pg_stat_activity
   WHERE datname='${APP}_test' AND pid <> pg_backend_pid();
  DROP DATABASE IF EXISTS ${APP}_test;
  CREATE DATABASE ${APP}_test TEMPLATE ${APP}_template;"

datistemplate=true lets CREATEDB users clone; datallowconn=false guards the baseline ([pg-tpl][pg-tpl]). Per-suite restore is file- system fast; per-test restore is OK under a few hundred tests, beyond that switch to wrap.

Mode 2 - wrap (BEGIN/ROLLBACK per test)

For schema-stable tests with an ORM exposing a transaction hook (Rails, Django, SQLAlchemy):

@pytest.fixture
def db_session(connection):
    transaction = connection.begin()
    yield connection
    transaction.rollback()

Per [pg-begin][pg-begin], BEGIN opens a transaction and ROLLBACK "discard[s] changes" - no disk touch, ~100× faster than restore for short tests. Nest with SAVEPOINT ... ROLLBACK TO SAVEPOINT.

wrap mode does NOT work when test code commits internally, runs non-transactional DDL, or spans multiple connections - fall back to restore.

Mode 3 - MySQL / MongoDB

# MySQL
mysqldump --single-transaction --routines --triggers ${APP}_template \
  > snapshots/${APP}_template.sql       # consistent InnoDB snapshot
mysql -e "DROP DATABASE IF EXISTS ${APP}_test; CREATE DATABASE ${APP}_test;"
mysql ${APP}_test < snapshots/${APP}_template.sql

# MongoDB
mongodump --db=${APP}_template --out=snapshots/
mongo --eval "db.getSiblingDB('${APP}_test').dropDatabase()"
mongorestore --db=${APP}_test snapshots/${APP}_template/

Prefer wrap mode when transactions support it (MySQL InnoDB does; MongoDB does for replica sets).

Mode 4 - pg_dump fallback (portable snapshots)

When the template-DB path is unavailable (baseline must be version-controlled, or user lacks CREATEDB), use pg_dump -Fc ([pg-dump][pg-dump]):

pg_dump -Fc --no-owner --no-privileges ${APP}_template > snap.dump
dropdb --if-exists ${APP}_test && createdb -T template0 ${APP}_test
pg_restore --no-owner -d ${APP}_test snap.dump

-Fc = compressed custom format; --no-owner --no-privileges strip environment-specific ALTER OWNER / GRANT ([pg-dump][pg-dump]). For large baselines use -Fd + -j <N> for parallel dump/restore. Slower than template clone but portable.

Output format

## DB snapshot/restore - `<app>` (`<engine>`)
**Mode:** snapshot | restore | wrap | list
**Strategy:** template-db | pg_dump | mysqldump | mongodump | begin/rollback
**Duration:** <N>ms  **Result:** OK | FAIL - <reason>
### Steps executed
1. ...
### Next steps
- (e.g.) Wire `scripts/restore-test-db.sh` into the Jest globalSetup hook.

Examples

  • Rails CI (~2000 tests; db:reset 12s × 6 = 72s) → build template once; per-suite restore ~400ms; CI cuts ~70s.
  • pytest + SQLAlchemy (~300 tests, stable schema) → wrap fixture with BEGIN/ROLLBACK at <5ms/test (~1.5s total).
  • Refused wrap: tests with non-transactional DDL (CREATE INDEX CONCURRENTLY) - cite the files; recommend restore.

Refuse-to-proceed rules

  • restore with no baseline → recommend snapshot first.
  • snapshot against a DB name not matching ${APP}_template → prevents accidental production capture.
  • restore against a DB whose name lacks test/_test/dev → defends against RESTORE production_db FROM template.
  • wrap when DDL is detected → cite files; recommend restore.

Anti-patterns

Anti-patternFix
db:migrate between every testSnapshot once; restore from template.
TRUNCATE to resetMisses seed/sequence state; use template clone.
DROP DATABASE without prior pg_terminate_backendBoot connections first (Mode 1).
One template shared across parallel CI workersPer-worker template (${APP}_template_${WORKER}).
wrap + test that commits internallyDetect commit(); require restore.
pg_dump without --no-owner --no-privilegesAlways strip when snapshot is portable.

Hand-off targets

  • Container-managed DBs → testcontainers (containers ~5 - 30s vs template restore ~50 - 500ms).
  • Compose stacks → docker-compose-tests (its migrate service produces the template state).
  • Parallel-isolation symptoms under -j N → e2e-flake-bisector in qa-flake-triage (shared-state isolation stage).

References

  • [pg-templates][pg-tpl], [pg-begin][pg-begin], [pg-dump][pg-dump] (Postgres template DBs; BEGIN / ROLLBACK; pg_dump -Fc/-Fd); docker-compose-tests (migrate-service pattern producing the template state).