safe-migrate 0.8.0

Check PostgreSQL migrations against a synchronized database baseline
Documentation
#!/bin/sh
# shellcheck disable=SC2016
set -eu

repo_root=$(CDPATH='' cd -- "$(dirname -- "$0")/.." && pwd)
test_tmp=$(mktemp -d)
trap 'rm -rf "$test_tmp"' EXIT HUP INT TERM

install_dir="$test_tmp/install"
output="$test_tmp/output"
command_dir="$test_tmp/commands"
mkdir "$command_dir"
ln -s "$(command -v uname)" "$command_dir/uname"

PATH="$command_dir" \
  /bin/sh "$repo_root/install.sh" \
  --dry-run \
  --version v0.4.2 \
  --target x86_64-unknown-linux-musl \
  --install-dir "$install_dir" \
  >"$output" 2>&1

grep -F '[dry-run] No network requests or filesystem changes will be made.' "$output" >/dev/null
grep -F 'safe-migrate-x86_64-unknown-linux-musl.tar.gz' "$output" >/dev/null
grep -F "atomically install ${install_dir}/safe-migrate" "$output" >/dev/null

if [ -e "$install_dir" ]; then
  printf '%s\n' 'dry run unexpectedly created the requested install directory' >&2
  exit 1
fi

archive_root="$test_tmp/archive"
archive="$test_tmp/safe-migrate-x86_64-unknown-linux-musl.tar.gz"
zip_archive="$test_tmp/safe-migrate-x86_64-pc-windows-msvc.zip"
mkdir "$archive_root"
printf '%s\n' '#!/bin/sh' 'printf "mock safe-migrate\n"' > "$archive_root/safe-migrate"
chmod +x "$archive_root/safe-migrate"
tar -czf "$archive" -C "$archive_root" safe-migrate
cp "$archive_root/safe-migrate" "$archive_root/safe-migrate.exe"
(cd "$archive_root" && zip -q "$zip_archive" safe-migrate.exe)

mock_dir="$test_tmp/mock-commands"
mkdir "$mock_dir"
for command_name in \
  uname tar gzip unzip sed head grep mktemp cp chmod mkdir mv rm cut tr cat find sha256sum
do
  ln -s "$(command -v "$command_name")" "$mock_dir/$command_name"
done

mock_curl="$mock_dir/curl"
printf '%s\n' \
  '#!/bin/sh' \
  'set -eu' \
  'output=""' \
  'url=""' \
  'while [ "$#" -gt 0 ]; do' \
  '  case "$1" in' \
  '    -o) output="$2"; shift 2 ;;' \
  '    -*) shift ;;' \
  '    *) url="$1"; shift ;;' \
  '  esac' \
  'done' \
  'case "$url" in' \
  '  *.tar.gz.sha256) exit 22 ;;' \
  '  *.sha256)' \
  '    case "$MOCK_CHECKSUM_MODE" in' \
  '      missing) exit 22 ;;' \
  '      network) exit 6 ;;' \
  '      malformed) printf "not-a-checksum  archive.tar.gz\n" > "$output" ;;' \
  '      mismatch) printf "%064d  archive.tar.gz\n" 0 > "$output" ;;' \
  '      valid)' \
  '        digest=$(sha256sum "$MOCK_ARCHIVE" | cut -d" " -f1)' \
  '        printf "%s  archive.tar.gz\n" "$digest" > "$output"' \
  '        ;;' \
  '      *) exit 2 ;;' \
  '    esac' \
  '    ;;' \
  '  *.tar.gz) cp "$MOCK_ARCHIVE" "$output" ;;' \
  '  *.zip) cp "$MOCK_ARCHIVE" "$output" ;;' \
  '  *) exit 22 ;;' \
  'esac' \
  > "$mock_curl"
chmod +x "$mock_curl"

run_mock_install() {
  checksum_mode="$1"
  expected_status="$2"
  case_dir="$test_tmp/$checksum_mode"
  case_output="$case_dir/output"
  case_install="$case_dir/install"
  mkdir "$case_dir"

  set +e
  PATH="$mock_dir" \
    MOCK_ARCHIVE="$archive" \
    MOCK_CHECKSUM_MODE="$checksum_mode" \
    /bin/sh "$repo_root/install.sh" \
      --version v0.4.2 \
      --target x86_64-unknown-linux-musl \
      --install-dir "$case_install" \
      >"$case_output" 2>&1
  status=$?
  set -e

  if [ "$status" -ne "$expected_status" ]; then
    printf 'installer case %s returned %s, expected %s\n' \
      "$checksum_mode" "$status" "$expected_status" >&2
    sed -n '1,80p' "$case_output" >&2
    exit 1
  fi

  case "$checksum_mode" in
    valid)
      test -x "$case_install/safe-migrate"
      ;;
    network)
      grep -F 'network failure downloading' "$case_output" >/dev/null
      test ! -e "$case_install/safe-migrate"
      ;;
    *)
      grep -F 'release integrity verification failed' "$case_output" >/dev/null
      test ! -e "$case_install/safe-migrate"
      ;;
  esac
}

run_mock_install missing 1
run_mock_install network 1
run_mock_install malformed 1
run_mock_install mismatch 1
run_mock_install valid 0

zip_case="$test_tmp/zip"
mkdir "$zip_case"
PATH="$mock_dir" \
  MOCK_ARCHIVE="$zip_archive" \
  MOCK_CHECKSUM_MODE=valid \
  /bin/sh "$repo_root/install.sh" \
    --version v0.4.2 \
    --target x86_64-pc-windows-msvc \
    --install-dir "$zip_case/install" \
    >"$zip_case/output" 2>&1
test -x "$zip_case/install/safe-migrate.exe"

if /bin/sh "$repo_root/install.sh" --dry-run --version v1.2.beta \
    --target x86_64-unknown-linux-musl >/dev/null 2>&1; then
  printf '%s\n' 'installer accepted a non-semantic version' >&2
  exit 1
fi
for invalid_version in v1.2.3. v.1.2.3 v1..3 v01.2.3; do
  if /bin/sh "$repo_root/install.sh" --dry-run --version "$invalid_version" \
      --target x86_64-unknown-linux-musl >/dev/null 2>&1; then
    printf 'installer accepted invalid version: %s\n' "$invalid_version" >&2
    exit 1
  fi
done
if /bin/sh "$repo_root/install.sh" --dry-run --version v1.2.3 \
    --target aarch64-pc-windows-msvc >/dev/null 2>&1; then
  printf '%s\n' 'installer accepted an unpublished target' >&2
  exit 1
fi

if find "$test_tmp" -name '.safe-migrate.tmp.*' -print -quit | grep -q .; then
  printf '%s\n' 'installer left a temporary destination behind' >&2
  exit 1
fi