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
| Setting | Source | Variable |
|---|---|---|
| Host | required — GitLab CI/CD variable or local .env | TEST_DB_HOST |
| Port | default 5000 if unset | TEST_DB_PORT |
| Database | required — GitLab CI/CD variable or local .env | TEST_DB_NAME |
| User | default postgres if unset | TEST_DB_USERNAME |
| Password | required — GitLab CI/CD variable or local .env | TEST_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
integrationcontext by default (skips the large concept dictionary seed). - The slow dictionary suite is tagged
slow-seed(-Dgroups=slow-seed) and usesintegration,concept-dictionary. - Merge note vs sample-CSV work: land this first; take only the divisions half from that MR
(
metadata/004context: "!integration"+metadata/009sample). Concept-sample for every suite is unnecessary once the test DB stays persistent.