Skip to main content

Dedicated test database

Integration and migration tests (local and CI) use a persistent PostgreSQL database on the LAN instead of Testcontainers. That matches how production runs Liquibase: new changesets against an already-migrated schema with accumulated data — not a clean slate every pipeline.

Connection

SettingSourceVariable
Hostrequired — GitLab CI/CD variable or local .envTEST_DB_HOST
Portdefault 5000 if unsetTEST_DB_PORT
Databaserequired — GitLab CI/CD variable or local .envTEST_DB_NAME
Userdefault postgres if unsetTEST_DB_USERNAME
Passwordrequired — GitLab CI/CD variable or local .envTEST_DB_PASSWORD

Helpers live in the common-testing module (…/DedicatedTestDatabase.java), consumed with <scope>test</scope> by common/core/imaging/workforce. That module is never a production compile dependency.

CI: set TEST_DB_HOST, TEST_DB_NAME, and TEST_DB_PASSWORD (and optionally port/username) as GitLab CI/CD variables. .gitlab-ci.yml does not commit them. test:backend probes reachability with pg_isready before Maven starts.

Local: put the same keys in a gitignored .env (see .env.example). Maven ITs resolve them via DedicatedTestDatabase without exporting into the shell.

Why persistent (not per-run isolation)

A fresh database per suite cannot catch checksum collisions on already-applied changesets, onFail: HALT preconditions against real data, or migrations that only break from current state. Those are the failures that matter in production.

Test-created rows are covered by Spring @Transactional rollback. Suites must own their data (unique keys / UUID prefixes) and must not assert global COUNT(*) against the whole table — assert that every expected seed id is present instead (extras from other suites are OK).

Recovery when the test DB is poisoned

If a half-applied changeset leaves the database unusable, rebuild it the same way as a local dev DB wipe — scripts/recreate-db.sh against the LAN host (requires ALLOW_REMOTE_DROP=1):

# Values from .env / CI variables — do not hard-code host or password in scripts you commit.
export DB_HOST="$TEST_DB_HOST" DB_PORT="${TEST_DB_PORT:-5000}" DB_NAME="$TEST_DB_NAME" \
DB_USERNAME="${TEST_DB_USERNAME:-postgres}" DB_PASSWORD="$TEST_DB_PASSWORD" ALLOW_REMOTE_DROP=1
scripts/recreate-db.sh --yes

./mvnw -pl app -am package -DskipTests
export DB_HOST="$TEST_DB_HOST" DB_PORT="${TEST_DB_PORT:-5000}" DB_NAME="$TEST_DB_NAME" \
DB_USERNAME="${TEST_DB_USERNAME:-postgres}" DB_PASSWORD="$TEST_DB_PASSWORD" \
KEYCLOAK_ENABLED=false LIQUIBASE_CONTEXTS=integration APP_PORT=8085
java -Xmx1g -jar app/target/platform-app-*.jar
# stop once "Started PlatformApplication" appears

Fast rebuild note: a full CIEL dictionary load (~368k rows) can take ~28 minutes. When sample reference CSVs land (divisions sample + optional concept-sample for rebuild-only), prefer those contexts for recovery (~78s). Default CI / MR pipelines keep the persistent DB and full dictionary where already seeded; from-empty bootstrap proof belongs to M14 / a rare nightly job, not every MR.

Local usage

# Ensure .env has TEST_DB_HOST, TEST_DB_NAME, TEST_DB_PASSWORD (see .env.example)
./mvnw -pl core -am test

Notes

  • Liquibase runs with the integration context by default (skips the large concept dictionary seed).
  • The slow dictionary suite is tagged slow-seed (-Dgroups=slow-seed) and uses integration,concept-dictionary.
  • Merge note vs sample-CSV work: land this first; take only the divisions half from that MR (metadata/004 context: "!integration" + metadata/009 sample). Concept-sample for every suite is unnecessary once the test DB stays persistent.