Files
member-console/test/reset-app-db.sh
T
cgalo5758 0b28a9dc29 Remediate security audit findings
- Replace gorilla/csrf with net/http CrossOriginProtection
- Require valkey-password and add TLS options for session store
- End session at /logout and revoke refresh tokens
- Re-derive identity and roles from provider every five minutes
- Process each Stripe webhook event in its own Temporal workflow
- Give each outbox entry its own workflow with Temporal retries
- Guard against stale Stripe events with provider timestamps
- Derive transport security from base-url scheme
2026-09-09 13:25:43 -05:00

131 lines
5.9 KiB
Bash
Executable File

#!/usr/bin/env bash
#
# test/reset-app-db.sh — restore the APPLICATION database to the demo state.
#
# The screen-capture utility (`make screens`, test/e2e/screens) runs this
# first so every capture comes from the same rows: walkthrough fixtures,
# manual experiments, drifted timestamps, and, because database-generated
# IDs are never pinned (Decision 30), freshly generated identifiers would
# otherwise show up as changed screens on pages nobody touched (maintainer
# decision 2026-08-30).
#
# The demo state is built once into a snapshot database (<app db>_demo):
# migrate, demo seed with the operator's own person (--with-operator), the
# boot data state, and the row clocks pinned (pin-timestamps.sql). Every
# reset then drops the application database and recreates it FROM that
# snapshot, so rows and their IDs are identical between resets. The snapshot
# is rebuilt when it is missing, when the migrations, the seed, or the clock
# pin have changed since it was built (their fingerprint is the snapshot's
# database comment), or on `--rebuild`.
#
# This is the one deliberate exception to the rule that nothing under test/
# touches `member_console`: reset-test-db.sh refuses that database so
# `make test` can never clobber a developer's working state; this script
# exists to clobber it on purpose, and only when asked. The running app
# reconnects on its next query; browser sessions that referenced the old
# rows are invalid until their next sign-in.
#
# Usage: cd test && ./reset-app-db.sh [--rebuild]
set -euo pipefail
here="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
repo_root="$(cd "$here/.." && pwd)"
env_file="$here/.env"
if [[ ! -f "$env_file" ]]; then
echo "error: $env_file not found. Run ./bootstrap-stack.sh first." >&2
exit 1
fi
set -a
# shellcheck source=/dev/null
source "$env_file"
set +a
for var in POSTGRES_ADMIN_DSN MC_DB_DSN; do
if [[ -z "${!var:-}" ]]; then
echo "error: $var is not set in $env_file." >&2
exit 1
fi
done
# The database reset re-creates every row from the demo seed (or, on
# rebuild, from a fresh seed run), and ids are UUIDv7 -- never reused -- so
# a session minted before the reset holds a person id that no longer exists
# afterward. flush_sessions empties the Valkey session store so no such
# stale session survives a reset; this Valkey instance exists only for
# member-console sessions in the test stack, so a full flush loses nothing
# else. Tried in order: a local valkey-cli, a local redis-cli (protocol-
# compatible), or docker compose exec into the valkey service when neither
# CLI is installed on the host.
flush_sessions() {
echo "Flushing the session store (Valkey) so no pre-reset session survives..."
# The store is password-protected (valkey-password is required
# configuration), so every path here authenticates.
local auth=()
[[ -n "${VALKEY_PASSWORD:-}" ]] && auth=(-a "$VALKEY_PASSWORD" --no-auth-warning)
if command -v valkey-cli >/dev/null 2>&1; then
valkey-cli -h localhost -p "$VALKEY_PORT" "${auth[@]}" FLUSHALL >/dev/null
elif command -v redis-cli >/dev/null 2>&1; then
redis-cli -h localhost -p "$VALKEY_PORT" "${auth[@]}" FLUSHALL >/dev/null
else
(cd "$here" && docker compose exec -T valkey valkey-cli "${auth[@]}" FLUSHALL >/dev/null)
fi
}
path="${MC_DB_DSN#*://}"; path="${path#*/}"; name="${path%%\?*}"
if [[ -z "$name" ]]; then
echo "error: cannot read a database name from MC_DB_DSN." >&2
exit 1
fi
snapshot="${name}_demo"
# The snapshot's DSN is the application's with the database name (the last
# path segment) swapped; the user name may equal the database name, so a
# plain substitution would rename the wrong part.
dsn_prefix="${MC_DB_DSN%/*}"
dsn_tail="${MC_DB_DSN##*/}"
dsn_query="${dsn_tail#"${name}"}"
snapshot_dsn="${dsn_prefix}/${snapshot}${dsn_query}"
# The snapshot is current when it was built from the migrations, the seed,
# and the clock pin the repository holds now; the fingerprint of those
# sources is stored as the snapshot database's comment.
fingerprint="$(cat "$repo_root"/internal/db/migrations/*.sql "$repo_root"/internal/demoseed/*.go "$here/pin-timestamps.sql" | md5sum | cut -c1-32)"
rebuild=0
if [[ "${1:-}" == "--rebuild" ]]; then
rebuild=1
else
current="$(psql "$POSTGRES_ADMIN_DSN" -tAc "SELECT coalesce(shobj_description(oid, 'pg_database'), '') FROM pg_database WHERE datname = '${snapshot}'")"
if [[ "$current" != "$fingerprint" ]]; then
echo "Demo snapshot '${snapshot}' is missing or was built from older migrations or seed; rebuilding it."
rebuild=1
fi
fi
if [[ "$rebuild" == 1 ]]; then
echo "Building the demo snapshot '${snapshot}' (migrate, seed with the operator, pin clocks)..."
psql "$POSTGRES_ADMIN_DSN" -v ON_ERROR_STOP=1 --quiet <<SQL
DROP DATABASE IF EXISTS ${name} WITH (FORCE);
CREATE DATABASE ${name} OWNER member_console;
SQL
(cd "$repo_root" && MC_DB_DSN="$MC_DB_DSN" go run . migrate up --config test/mc-config.yaml >/dev/null)
"$here/seed-demo.sh" --with-operator >/dev/null
psql "$MC_DB_DSN" -v ON_ERROR_STOP=1 --quiet <<SQL
SET session_replication_role = replica;
\\i $here/pin-timestamps.sql
SQL
psql "$POSTGRES_ADMIN_DSN" -v ON_ERROR_STOP=1 --quiet <<SQL
DROP DATABASE IF EXISTS ${snapshot} WITH (FORCE);
CREATE DATABASE ${snapshot} OWNER member_console;
SQL
pg_dump "$MC_DB_DSN" --no-owner --no-privileges | psql "$snapshot_dsn" -v ON_ERROR_STOP=1 --quiet >/dev/null
psql "$POSTGRES_ADMIN_DSN" -v ON_ERROR_STOP=1 --quiet -c "COMMENT ON DATABASE ${snapshot} IS '${fingerprint}'"
echo "Demo snapshot '${snapshot}' built; application database '${name}' is at the demo state."
flush_sessions
exit 0
fi
echo "Restoring application database '${name}' from the demo snapshot '${snapshot}'..."
psql "$POSTGRES_ADMIN_DSN" -v ON_ERROR_STOP=1 --quiet <<SQL
DROP DATABASE IF EXISTS ${name} WITH (FORCE);
CREATE DATABASE ${name} TEMPLATE ${snapshot} OWNER member_console;
SQL
echo "Application database '${name}' is at the demo state."
flush_sessions