#!/usr/bin/env bash set -euo pipefail repo_root=$(cd -- "$(dirname -- "$0")/.." && pwd) backup_script="$repo_root/deploy/pilot/backup.sh" test_parent=$(cd -- /tmp && pwd -P) test_root=$(mktemp -d "$test_parent/krona-backup-test.XXXXXX") test_root=$(cd -- "$test_root" && pwd -P) is_test_root() { [ "$(dirname -- "$1")" = "$test_parent" ] || return 1 case "$(basename -- "$1")" in krona-backup-test.*) return 0 ;; *) return 1 ;; esac } if ! is_test_root "$test_root"; then printf 'unexpected temporary test directory: %s\n' "$test_root" >&2 exit 1 fi mock_bin="$test_root/bin" mkdir -p "$mock_bin" cleanup() { resolved_test_root=$(cd -- "$test_root" && pwd -P) || return 1 if ! is_test_root "$resolved_test_root"; then printf 'refusing to remove unexpected test directory: %s\n' "$resolved_test_root" >&2 return 1 fi rm -rf -- "$resolved_test_root" } trap cleanup EXIT cat > "$mock_bin/docker" <<'MOCK_DOCKER' #!/bin/sh set -eu if [ "${MOCK_DOCKER_FAIL:-0}" = 1 ]; then printf 'synthetic partial dump\n' exit 42 fi printf 'synthetic PostgreSQL dump\n' MOCK_DOCKER cat > "$mock_bin/scp" <<'MOCK_SCP' #!/bin/sh set -eu printf '%s\n' "$@" > "$MOCK_SCP_LOG" if [ "${MOCK_SCP_FAIL:-0}" = 1 ]; then exit 43 fi MOCK_SCP cat > "$mock_bin/openssl" <<'MOCK_OPENSSL' #!/bin/sh set -eu if [ "${MOCK_OPENSSL_CMS_FAIL:-0}" = 1 ] && [ "${1:-}" = cms ] && [ "${2:-}" = -encrypt ]; then exit 44 fi exec /usr/bin/openssl "$@" MOCK_OPENSSL chmod +x "$mock_bin/docker" "$mock_bin/scp" "$mock_bin/openssl" cert="$test_root/recipient.crt" key="$test_root/recipient.key" /usr/bin/openssl req -x509 -newkey rsa:2048 -nodes -sha256 -days 1 \ -keyout "$key" -out "$cert" -subj '/CN=backup-test-recipient' >/dev/null 2>&1 export PATH="$mock_bin:$PATH" export BACKUP_RECIPIENT_CERT="$cert" export BACKUP_REMOTE='backup@example.invalid:/backup/krona-staff/' export MOCK_SCP_LOG="$test_root/scp-args.log" assert_no_plain_dump() { backup_dir=$1 if find "$backup_dir" -type f ! -name '*.dump.cms' ! -name '*.dump.cms.sha256' | grep -q .; then printf 'unexpected non-encrypted backup artifact in %s\n' "$backup_dir" >&2 exit 1 fi } run_backup() { backup_dir=$1 shift mkdir -p "$backup_dir" if env BACKUP_DIRECTORY="$backup_dir" "$@" sh "$backup_script"; then return 0 else return $? fi } sh -n "$backup_script" success_dir="$test_root/success" run_backup "$success_dir" env encrypted=$(find "$success_dir" -maxdepth 1 -type f -name '*.dump.cms' -print -quit) test -n "$encrypted" test -s "$encrypted.sha256" (cd "$success_dir" && sha256sum -c "$(basename -- "$encrypted").sha256") >/dev/null cmp <(printf 'synthetic PostgreSQL dump\n') \ <(/usr/bin/openssl cms -decrypt -binary -inform DER -in "$encrypted" -recip "$cert" -inkey "$key") assert_no_plain_dump "$success_dir" test "$(wc -l < "$MOCK_SCP_LOG")" -eq 4 pull_dir="$test_root/external-pull" pull_output=$(run_backup "$pull_dir" env BACKUP_TRANSFER_MODE=external-pull BACKUP_REMOTE=) test "$(find "$pull_dir" -maxdepth 1 -type f | wc -l)" -eq 2 printf '%s\n' "$pull_output" | grep -q '^off_host_copy=pending_pull$' assert_no_plain_dump "$pull_dir" test "$(wc -l < "$MOCK_SCP_LOG")" -eq 4 assert_failure_cleans_scratch() { backup_dir=$1 if find "$backup_dir" -mindepth 1 -print -quit | grep -q .; then printf 'unexpected artifact after failed backup in %s\n' "$backup_dir" >&2 exit 1 fi } dump_fail_dir="$test_root/pg-dump-failure" if run_backup "$dump_fail_dir" env MOCK_DOCKER_FAIL=1; then printf 'backup succeeded after mocked pg_dump failure\n' >&2 exit 1 fi assert_failure_cleans_scratch "$dump_fail_dir" openssl_fail_dir="$test_root/openssl-failure" if run_backup "$openssl_fail_dir" env MOCK_OPENSSL_CMS_FAIL=1; then printf 'backup succeeded after mocked CMS encryption failure\n' >&2 exit 1 fi assert_failure_cleans_scratch "$openssl_fail_dir" scp_fail_dir="$test_root/scp-failure" if run_backup "$scp_fail_dir" env MOCK_SCP_FAIL=1; then printf 'backup succeeded after mocked scp failure\n' >&2 exit 1 fi assert_no_plain_dump "$scp_fail_dir" test "$(find "$scp_fail_dir" -maxdepth 1 -type f | wc -l)" -eq 2 printf 'backup tests passed: syntax, CMS round-trip, pg_dump/OpenSSL/scp failures, no plaintext dump on disk\n'