diff --git a/automation/refresh-test-db.sh b/automation/refresh-test-db.sh index 88f01aa..cb28141 100644 --- a/automation/refresh-test-db.sh +++ b/automation/refresh-test-db.sh @@ -26,11 +26,22 @@ if [ "$TEST_URL" = "$PROD_URL" ]; then fi log "Refreshing $TEST_DB from $PROD_DB ..." -# --clean --if-exists drops+recreates each object in place (first run on an empty -# DB is a no-op for the DROPs). --no-owner/--no-privileges keep it portable. + +# Safety: only ever drop/restore the derived TEST database, never prod. +TEST_DBNAME=$(printf '%s' "$TEST_URL" | sed -E 's#.*/([^/?]+)([?].*)?$#\1#') +[ "$TEST_DBNAME" = "$TEST_DB" ] || { log "ERROR: refusing to refresh '$TEST_DBNAME' (expected '$TEST_DB')"; exit 1; } + +# Fully reset the test schema BEFORE restoring. `pg_dump --clean` only drops the +# objects that exist in PROD, so anything created on the test DB by a previously +# applied UNRELEASED migration (e.g. crewing tables/types/enum values not yet in +# prod) would survive as a leftover — and then collide with the `migrate deploy` +# replay below ("type already exists", P3009), blocking every later migration. +# Dropping the schema first guarantees the restore brings exactly prod's objects +# and the unreleased migrations apply cleanly. (No-op on an already-empty DB.) errfile=$(mktemp) -pg_dump --clean --if-exists --no-owner --no-privileges "$PROD_URL" \ - | psql "$TEST_URL" >/dev/null 2>"$errfile" +psql "$TEST_URL" -v ON_ERROR_STOP=1 -c "DROP SCHEMA public CASCADE; CREATE SCHEMA public;" >/dev/null 2>"$errfile" +pg_dump --no-owner --no-privileges "$PROD_URL" \ + | psql "$TEST_URL" >/dev/null 2>>"$errfile" prod_tables=$(psql -tAc "SELECT count(*) FROM information_schema.tables WHERE table_schema='public';" "$PROD_URL") test_tables=$(psql -tAc "SELECT count(*) FROM information_schema.tables WHERE table_schema='public';" "$TEST_URL")