#!/usr/bin/env bash
# Usage: . testlib.sh
# Simple shell command language test library.
#
# Tests must follow the basic form:
#
#   begin_test "the thing"
#   (
#      set -e
#      echo "hello"
#      false
#   )
#   end_test
#
# When a test fails its stdout and stderr are shown.
#
# Note that tests must `set -e' within the subshell block or failed assertions
# will not cause the test to fail and the result may be misreported.
#
# Copyright (c) 2011-14 by Ryan Tomayko <http://tomayko.com>
# License: MIT
# shellcheck disable=SC2319
set -e

# Setting basic paths
ROOTDIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )/.." && pwd )"
PATH="$ROOTDIR/test/bin:$ROOTDIR/bin:$ROOTDIR/share/github-backup-utils:$PATH"

# create a temporary work space
TMPDIR="$ROOTDIR/test/tmp"
TRASHDIR="$TMPDIR/$(basename "$0")-$$"

test_suite_file_name="$(basename "${BASH_SOURCE[1]}")"
test_suite_name="${GHE_TEST_SUITE_NAME:-${test_suite_file_name%.*}}"
results_file="$TMPDIR/results"
test_suite_before_time=$(date '+%s.%3N')

# Set GIT_{AUTHOR,COMMITTER}_{NAME,EMAIL}
# This removes the assumption that a git config that specifies these is present.
export GIT_AUTHOR_NAME=make GIT_AUTHOR_EMAIL=make GIT_COMMITTER_NAME=make GIT_COMMITTER_EMAIL=make

# Point commands at the test backup.config file
GHE_BACKUP_CONFIG="$ROOTDIR/test/backup.config"
GHE_DATA_DIR="$TRASHDIR/data"
GHE_REMOTE_DATA_DIR="$TRASHDIR/remote/data"
GHE_REMOTE_ROOT_DIR="$TRASHDIR/remote"
export GHE_BACKUP_CONFIG GHE_DATA_DIR GHE_REMOTE_DATA_DIR GHE_REMOTE_ROOT_DIR

# The default remote appliance version. This may be set in the environment prior
# to invoking tests to emulate a different remote vm version.
: ${GHE_TEST_REMOTE_VERSION:=3.12.0.rc1}
export GHE_TEST_REMOTE_VERSION

# Source in the backup config and set GHE_REMOTE_XXX variables based on the
# remote version established above or in the environment.
# shellcheck source=share/github-backup-utils/ghe-backup-config
. "$( dirname "${BASH_SOURCE[0]}" )/../share/github-backup-utils/ghe-backup-config"
ghe_parse_remote_version "$GHE_TEST_REMOTE_VERSION"
ghe_remote_version_config "$GHE_TEST_REMOTE_VERSION"

# Unset special variables meant to be inherited by individual ghe-backup or
# ghe-restore process groups
unset GHE_SNAPSHOT_TIMESTAMP

# Color definitions for log output
color_reset=$(printf '\e[0m')
# Display commands (lines starting with + in the output) in purple
color_command=$(printf '\e[0;35m')
# Display exit code line in red
color_error_message=$(printf '\e[0;31m')
# Display test suite name in blue
color_test_suite=$(printf '\e[0;34m')
# Display successful tests in bold green
color_pass=$(printf '\e[1;32m')
# Display skipped tests in bold gray
color_skip=$(printf '\e[1;37m')
# Display failed tests in bold red
color_fail=$(printf '\e[1;31m')

# keep track of num tests and failures
tests=0
successes=0
skipped=0
failures=0

# this runs at process exit
atexit () {
  res=$?

  test_suite_after_time=$(date '+%s.%3N')
  test_suite_elapsed_time=$(echo "scale=3; $test_suite_after_time - $test_suite_before_time" | bc)

  # Temporarily redirect stdout output to results file
  exec 3<&1
  exec 1>>"$results_file"

  # Print test summary for this test suite
  echo -n "| $test_suite_name | "

  if [ "$failures" -eq "0" ]; then
    echo -n ":green_circle: passed"
  else
    echo -n ":red_circle: failed"
  fi

  printf " | $successes | $failures | $skipped | %.3f s |\\n" "$test_suite_elapsed_time"

  # Restore stdout
  exec 1<&3

  [ -z "$KEEPTRASH" ] && rm -rf "$TRASHDIR"
  if [ $failures -gt 0 ]; then
    exit 1
  elif [ $res -ne 0 ]; then
    exit $res
  else
    exit 0
  fi
}

# create the trash dir and data dirs
trap "atexit" EXIT
mkdir -p "$TRASHDIR" "$GHE_DATA_DIR" "$GHE_REMOTE_DATA_DIR" "$GHE_REMOTE_DATA_USER_DIR"
cd "$TRASHDIR"

# Put remote metadata file in place for ghe-host-check which runs with pretty
# much everything. You can pass a version number in the first argument to test
# with different remote versions.
setup_remote_metadata () {
  mkdir -p "$GHE_REMOTE_DATA_DIR" "$GHE_REMOTE_DATA_USER_DIR"
  mkdir -p "$GHE_REMOTE_DATA_USER_DIR/common"
  mkdir -p "$GHE_REMOTE_ROOT_DIR/etc/github"
  # Create fake remote repositories dir
  mkdir -p "$GHE_REMOTE_DATA_USER_DIR/repositories"
  echo "4265cb52-c49f-4611-b12a-410ab5902861" > "$GHE_REMOTE_DATA_USER_DIR/common/uuid"
}
setup_remote_metadata

setup_remote_license () {
  mkdir -p "$(dirname "$GHE_REMOTE_LICENSE_FILE")"
  echo "fake license data" > "$GHE_REMOTE_LICENSE_FILE"
}
setup_remote_license

setup_remote_cluster () {
  mkdir -p "$GHE_REMOTE_ROOT_DIR/etc/github"
  touch "$GHE_REMOTE_ROOT_DIR/etc/github/cluster"
  echo "fake cluster config" > "$GHE_REMOTE_DATA_USER_DIR/common/cluster.conf"
}

# Put the necessary files in place to mimic a configured, or not, instance into
# maintenance mode.
#
# Pass anything as the first argument to "configure" the instance
setup_maintenance_mode () {
  configured=$1
  if [ -n "$configured" ]; then
    # create file used to determine if instance has been configured.
    touch "$GHE_REMOTE_ROOT_DIR/etc/github/configured"
  fi

  # create file used to determine if instance is in maintenance mode.
  mkdir -p "$GHE_REMOTE_DATA_DIR/github/current/public/system"
  touch "$GHE_REMOTE_DATA_DIR/github/current/public/system/maintenance.html"

  # Create fake remote repositories dir
  mkdir -p "$GHE_REMOTE_DATA_USER_DIR/repositories"
}

# Moves the instance out of maintenance mode.
disable_maintenance_mode () {
  # Remove file used to determine if instance is in maintenance mode.
  rm "$GHE_REMOTE_DATA_DIR/github/current/public/system/maintenance.html"
}

# Mark the beginning of a test. A subshell should immediately follow this
# statement.
begin_test () {
  test_status=$?
  [ -n "$test_description" ] && end_test $test_status
  unset test_status

  tests=$(( tests + 1 ))
  test_description="$1"

  exec 3>&1 4>&2
  out="$TRASHDIR/out"
  exec 1>"$out" 2>&1

  # allow the subshell to exit non-zero without exiting this process
  set -x +e
  before_time=$(date '+%s.%3N')

  # Marker to truncate the actual test output later
  echo "begin_test_truncate_marker" > /dev/null
}

report_failure_output () {
  echo "::group::Output of failed test" 1>&2
  # Truncate the test output to exclude testing-related instructions
  echo "$(<"$TRASHDIR/out")" \
    | sed '0,/begin_test_truncate_marker/d' \
    | sed -n '/end_test_truncate_marker/q;p' | head -n -2 \
    | sed "s/^\(+.*\)$/${color_command}\1${color_reset}/" \
    1>&2
  echo -e "\n${color_error_message}Test failed. The last command exited with exit code" \
    "$test_status.${color_reset}" 1>&2
  echo "::endgroup::" 1>&2
}

# Mark the end of a test.
end_test () {
  test_status="${1:-$?}"

  # Marker to truncate the actual test output later
  echo "end_test_truncate_marker" > /dev/null

  after_time=$(date '+%s.%3N')
  elapsed_time=$(echo "scale=3; $after_time - $before_time" | bc)
  set +x -e
  exec 1>&3 2>&4

  if [ "$test_status" -eq 0 ]; then
    successes=$(( successes + 1 ))
    printf "${color_pass}PASS${color_reset}" 1>&2
  elif [ "$test_status" -eq 254 ]; then
    skipped=$(( skipped + 1 ))
    printf "${color_skip}SKIP${color_reset}" 1>&2
  else
    failures=$(( failures + 1 ))
    printf "${color_fail}FAIL${color_reset}" 1>&2
  fi

  printf " [%8.3f s] ${color_test_suite}$test_suite_name${color_reset} $test_description\\n" \
    "$elapsed_time" 1>&2

  if [ "$test_status" -ne 0 ] && [ "$test_status" -ne 254 ]; then
    report_failure_output
  fi

  unset test_description
}

skip_test() {
  exit 254
}

# Create dummy data used for testing
# This same method can be used to generate the data used for testing backups
# and restores by passing in the appropriate location, for example:
#
# Testing backups: setup_test_data $GHE_REMOTE_DATA_USER_DIR
# Testing restores: setup_test_data "$GHE_DATA_DIR/1"
#
setup_test_data () {
  local loc=$1

  # Create some fake pages data in the remote data directory
  mkdir -p "$loc/pages"
  cd "$loc/pages"
  export pages1="4/c8/1e/72/2/legacy"
  export pages2="4/c1/6a/53/31/dd3a9a0faa88c714ef2dd638b67587f92f109f96"
  mkdir -p "$pages1" "$pages2"
  touch "$pages1/index.html" "$pages2/index.html"

  # Create a fake manage password file§
  mkdir -p "$GHE_REMOTE_DATA_USER_DIR/common"
  git config -f "$GHE_REMOTE_DATA_USER_DIR/common/secrets.conf" secrets.manage "fake password hash data"
  git config -f "$GHE_REMOTE_DATA_USER_DIR/common/secrets.conf" secrets.manage-auth.argon-secret "fake argon2 secret"

  # Create a fake password pepper file
  mkdir -p "$GHE_REMOTE_DATA_USER_DIR/common"
  git config -f "$GHE_REMOTE_DATA_USER_DIR/common/secrets.conf" secrets.github.user-password-secrets "fake password pepper data"

  # Create some fake hooks in the remote data directory
  mkdir -p "$loc/git-hooks/environments/tarballs"
  mkdir -p "$loc/git-hooks/repos"

  cd "$loc/git-hooks/environments"
  mkdir -p 123/abcdef 456/fed314
  touch 123/abcdef/script.sh 456/fed314/foo.sh

  cd "$loc/git-hooks/environments/tarballs"
  mkdir -p 987/qwert 765/frodo
  tar -C "$loc/git-hooks/environments/123/abcdef/" -zcf "$loc/git-hooks/environments/tarballs/987/qwert/script.tar.gz" ./
  tar -C "$loc/git-hooks/environments/456/fed314/" -zcf "$loc/git-hooks/environments/tarballs/765/frodo/foo.tar.gz" ./

  cd "$loc/git-hooks/repos"
  mkdir -p 321 654
  touch 321/script.sh 654/foo.sh

  mkdir -p "$loc/storage/"
  cd "$loc/storage/"
  object1="2/20/e1"
  object2="8/80/76"
  object3="e/ed/1a"
  mkdir -p "$object1" "$object2" "$object3"
  touch "$object1/20e1b33c19d81f490716c470c0583772b05a153831d55441cc5e7711eda5a241"
  touch "$object2/80766a2b18a96b9a5927ebdd980dc8d0820bea7ff0897b1b119af4bf20974d32"
  touch "$object3/ed1aa60f0706cefde8ba2b3be662d3a0e0e1fbc94a52a3201944684cc0c5f244"

  common=
  if [ "$loc" = "$GHE_REMOTE_DATA_USER_DIR" ]; then
    common="common"
  fi
  # Create a fake UUID
  echo "fake-uuid" > "$loc/$common/uuid"

  # Create fake audit log migration sentinel file
  touch "$loc/$common/es-scan-complete"

  # Create some fake elasticsearch data in the remote data directory
  mkdir -p "$loc/elasticsearch/gh-enterprise-es/node/0"
  cd "$loc/elasticsearch"
  touch gh-enterprise-es/node/0/stuff1
  touch gh-enterprise-es/node/0/stuff2

  # Create fake audit-logs
  this_yr=$(date +"%Y")
  this_mth=$(date +"%-m")
  last_mth=$(( $this_mth - 1 ))
  last_yr=$this_yr
  if [ "$last_mth" = 0 ]; then
    last_mth=12
    last_yr=$(( $this_yr - 1 ))
  fi

  mkdir -p "$loc/audit-log/"
  cd "$loc/audit-log/"
  echo "fake audit log last yr last mth" | gzip > audit_log-1-$last_yr-$last_mth-1.gz
  echo "1" > audit_log-1-$last_yr-$last_mth-1.size
  echo "fake audit log this yr this mth" | gzip > audit_log-1-$this_yr-$this_mth-1.gz
  echo "1" > audit_log-1-$this_yr-$this_mth-1.size

  # Create some test repositories in the remote repositories dir
  mkdir -p "$loc/repositories/info"
  mkdir -p "$TRASHDIR/hooks"

  cd "$loc/repositories"
  echo "fake nw-layout" > info/nw-layout
  echo "fake svn-v4-upgrade" > info/svn-v4-upgraded

  repo1="0/nw/01/aa/3f/1234/1234.git"
  repo2="0/nw/01/aa/3f/1234/1235.git"
  repo3="1/nw/23/bb/4c/2345/broken.git"
  mkdir -p "$repo1" "$repo2" "$repo3"

  wiki1="0/nw/01/aa/3f/1234/1234.wiki.git"
  mkdir -p "$wiki1"

  gist1="0/01/aa/3f/gist/93069ad4c391b6203f183e147d52a97a.git"
  gist2="1/23/bb/4c/gist/1234.git"
  mkdir -p "$gist1" "$gist2"

  # Initialize test repositories with a fake commit
  while IFS= read -r -d '' repo; do
    git init -q --bare "$repo"
    git --git-dir="$repo" --work-tree=. commit -q --allow-empty -m 'test commit'
    rm -rf "$repo/hooks"
    ln -s "$TRASHDIR/hooks" "$repo/hooks"
  done <   <(find . -type d -name '*.git' -prune -print0)

  # Add some fake svn data to repo2
  echo "fake svn history data" > "$repo2/svn.history.msgpack"
  mkdir "$repo2/svn_data"
  echo "fake property history data" > "$repo2/svn_data/property_history.msgpack"

  # Break a repo to test fsck
  rm -f $repo3/objects/4b/825dc642cb6eb9a060e54bf8d69288fbee4904

  if [ "$loc" != "$GHE_REMOTE_DATA_USER_DIR" ]; then
    # create a fake backups for each datastore
    if ! $SKIP_MYSQL; then
      echo "fake ghe-export-mysql data" | gzip > "$loc/mysql.sql.gz"
    fi
    echo "fake ghe-export-redis data" > "$loc/redis.rdb"
    echo "fake ghe-export-authorized-keys data" > "$loc/authorized-keys.json"
    echo "fake ghe-export-ssh-host-keys data" > "$TRASHDIR/ssh-host-keys"
    tar -C $TRASHDIR -cf "$loc/ssh-host-keys.tar" ssh-host-keys
    echo "fake ghe-export-settings data" > "$loc/settings.json"
    echo "fake ghe-export-ssl-ca-certificates data" > "$loc/ssl-ca-certificates.tar"
    echo "fake license data" > "$loc/enterprise.ghl"
    echo "fake password hash data" > "$loc/manage-password"
    echo "fake argon2 secret" > "$loc/manage-argon-secret"
    echo "fake password pepper data" > "$loc/password-pepper"
    echo "rsync" > "$loc/strategy"
    echo "$GHE_REMOTE_VERSION" >  "$loc/version"
  fi

  setup_minio_test_data "$GHE_DATA_DIR"
}

# Sets up test data for testing incremental restores.
setup_incremental_restore_data() {
  local full="$GHE_DATA_DIR/1"
  local inc_1="$GHE_DATA_DIR/2"
  local inc_2="$GHE_DATA_DIR/3"
  # Run the setup_test_data function to create three directories: 1 for full backup and two incremental.
  # we can use these directories for different types of tests
  setup_test_data "$full"
  setup_test_data "$inc_1"
  setup_test_data "$inc_2" 
  # Setup the metadata files that track which files are used to track full and incremental files
  echo "$full" >> "$GHE_DATA_DIR/inc_full_backup"
  echo -e "$inc_1\n$inc_2" >> "$GHE_DATA_DIR/inc_snapshot_data"
  # Configure lsn data in xtrabackup_checkpoints for the full backup and the incremental backup
  setup_incremental_lsn $full 1 100 full
  setup_incremental_lsn $inc_1 101 200 incremental
  setup_incremental_lsn $inc_2 201 300 incremental
}

setup_incremental_lsn() {
  local loc=$1
  local start=$2
  local end=$3
  local type=$4

cat <<LSN >> "$loc/xtrabackup_checkpoints"
backup_type = $type
from_lsn = $start
to_lsn = $end
last_lsn = $end
flushed_lsn = $end
LSN
}

setup_incremental_backup_config() {
  ghe-ssh "$GHE_HOSTNAME" -- 'mkdir -p /tmp/lsndir'
  ghe-ssh "$GHE_HOSTNAME" -- 'echo "fake xtrabackup checkpoint" > /tmp/lsndir/xtrabackup_checkpoints'

  enable_binary_backup
  export GHE_INCREMENTAL_MAX_BACKUPS=10
}

setup_actions_test_data() {
  local loc=$1

  if [ "$loc" != "$GHE_REMOTE_DATA_USER_DIR" ]; then
    mkdir -p "$loc/mssql"
    echo "fake ghe-export-mssql full data" > "$loc/mssql/mssql.bak"
    echo "fake ghe-export-mssql diff data" > "$loc/mssql/mssql.diff"
    echo "fake ghe-export-mssql tran data" > "$loc/mssql/mssql.log"
  else
    mkdir -p "$loc/mssql/backups"
    echo "fake mssql full data" > "$loc/mssql/backups/mssql.bak"
    echo "fake mssql diff data" > "$loc/mssql/backups/mssql.diff"
    echo "fake mssql tran data" > "$loc/mssql/backups/mssql.log"
  fi

  # Setup fake Actions data
  mkdir -p "$loc/actions/certificates"
  mkdir -p "$loc/actions/states"
  echo "fake actions certificate" > "$loc/actions/certificates/cert.cer"
  echo "fake actions state file" > "$loc/actions/states/actions_state"
}

cleanup_actions_test_data() {
  local loc=$1

  rm -rf "$loc/mssql"
  rm -rf "$loc/actions"
}

setup_minio_test_data() {
  local loc=$1

  mkdir -p "$loc/minio/"
  cd "$loc/minio/"
  bucket="packages"

  mkdir -p "$bucket"
}

cleanup_minio_test_data() {
  local loc=$1

  rm -rf "$loc/minio"
}

# A unified method to check everything backed up or restored during testing.
# Everything tested here should pass regardless of whether we're testing a backup
# or a restore.
verify_common_data() {
  # verify all repository data was transferred
  diff -ru "$GHE_REMOTE_DATA_USER_DIR/repositories" "$GHE_DATA_DIR/current/repositories"

  # verify all pages data was transferred
  diff -ru "$GHE_REMOTE_DATA_USER_DIR/pages" "$GHE_DATA_DIR/current/pages"

  # verify all git hooks tarballs were transferred
  diff -ru "$GHE_REMOTE_DATA_USER_DIR/git-hooks/environments/tarballs" "$GHE_DATA_DIR/current/git-hooks/environments/tarballs"

  # verify the extracted environments were not transferred
  ! diff -ru "$GHE_REMOTE_DATA_USER_DIR/git-hooks/environments" "$GHE_DATA_DIR/current/git-hooks/environments"

  # verify the extracted repositories were transferred
  diff -ru "$GHE_REMOTE_DATA_USER_DIR/git-hooks/repos" "$GHE_DATA_DIR/current/git-hooks/repos"

  if is_actions_enabled; then
    # verify mssql backups were transferred
    diff -ru "$GHE_REMOTE_DATA_USER_DIR/mssql/backups" "$GHE_DATA_DIR/current/mssql"
  fi

  if is_minio_enabled; then
    # verify minio object storge backups were transferred
    diff -ru "$GHE_REMOTE_DATA_USER_DIR/minio" "$GHE_DATA_DIR/minio"
  fi

  # tests that differ for cluster and single node backups and restores
  if [ "$(cat $GHE_DATA_DIR/current/strategy)" = "rsync" ]; then
    # verify the UUID was transferred
    diff -ru "$GHE_REMOTE_DATA_USER_DIR/common/uuid" "$GHE_DATA_DIR/current/uuid"

    # verify the audit log migration sentinel file has been created on 2.9 and above
    if [ "$GHE_VERSION_MAJOR" -eq 2 ] && [ "$GHE_VERSION_MINOR" -ge 9 ]; then
      diff -ru "$GHE_REMOTE_DATA_USER_DIR/common/es-scan-complete" "$GHE_DATA_DIR/current/es-scan-complete"
    fi
  fi
}

# A unified method to check everything backed up when performing a full backup
# during testing.
verify_all_backedup_data() {
  set -e
  # check that current symlink was created
  [ -d "$GHE_DATA_DIR/current" ]

  # check that the version file was written
  [ -f "$GHE_DATA_DIR/current/version" ]
  [ "$(cat "$GHE_DATA_DIR/current/version")" = "v$GHE_TEST_REMOTE_VERSION" ]

  # check that the strategy file was written
  [ -f "$GHE_DATA_DIR/current/strategy" ]

  # check that settings were backed up
  [ "$(cat "$GHE_DATA_DIR/current/settings.json")" = "fake ghe-export-settings data" ]

  # check that mysql data was backed up
  if ! $SKIP_MYSQL; then
    [ "$(gzip -dc < "$GHE_DATA_DIR/current/mysql.sql.gz")" = "fake ghe-export-mysql data" ]
  fi

  # check that redis data was backed up
  [[ "$(cat "$GHE_DATA_DIR/current/redis.rdb")" == *"fake redis data"* ]]

  # check that ssh public keys were backed up
  [ "$(cat "$GHE_DATA_DIR/current/authorized-keys.json")" = "fake ghe-export-authorized-keys data" ]

  # check that ssh host key was backed up
  [ "$(tar xfO "$GHE_DATA_DIR/current/ssh-host-keys.tar" ssh-host-keys)" = "fake ghe-export-ssh-host-keys data" ]

  # verify manage-password file was backed up
  [ "$(cat "$GHE_DATA_DIR/current/manage-password")" = "fake password hash data" ]

  # verify manage-argon-secret file was backed up
  if [ "$(version $GHE_REMOTE_VERSION)" -ge "$(version 3.8.0)" ]; then
    [ "$(cat "$GHE_DATA_DIR/current/manage-argon-secret")" = "fake argon2 secret" ]
  fi

  # verify password pepper file was backed up
  [ "$(cat "$GHE_DATA_DIR/current/password-pepper")" = "fake password pepper data" ]

  # check that ca certificates were backed up
  [ "$(cat "$GHE_DATA_DIR/current/ssl-ca-certificates.tar")" = "fake ghe-export-ssl-ca-certificates data" ]

  if is_actions_enabled; then
    # check that mssql databases were backed up
    [ "$(cat "$GHE_DATA_DIR/current/mssql/mssql.bak")" = "fake mssql full data" ]
    [ "$(cat "$GHE_DATA_DIR/current/mssql/mssql.diff")" = "fake mssql diff data" ]
    [ "$(cat "$GHE_DATA_DIR/current/mssql/mssql.log")" = "fake mssql tran data" ]
  fi

  # verify that ghe-backup wrote its version information to the host
  [ -f "$GHE_REMOTE_DATA_USER_DIR/common/backup-utils-version" ]

  # tests that differ for cluster and single node backups
  if [ -f "$GHE_REMOTE_DATA_USER_DIR/common/cluster.conf" ]; then
    grep -q "fake cluster config" "$GHE_DATA_DIR/current/cluster.conf"
    # verify strategy used
    [ "$(cat "$GHE_DATA_DIR/current/strategy")" = "cluster" ]
  else
    # verify strategy used
    [ "$(cat "$GHE_DATA_DIR/current/strategy")" = "rsync" ]

    # verify all ES data was transferred from live directory - not for cluster backups
    diff -ru "$GHE_REMOTE_DATA_USER_DIR/elasticsearch" "$GHE_DATA_DIR/current/elasticsearch"
  fi

  # verify common data
  verify_common_data
}

# A unified method to make sure post backup, the cleanup process works
verify_progress_cleanup_process() {
  set -e
  sudo -u nobody rm -rf /tmp/backup-utils-progress*/*
}

# A unified method to check everything restored when performing a full restore
# during testing.
verify_all_restored_data() {
  set -e

  # verify all import scripts were run
  if ! $SKIP_MYSQL; then
    grep -q "fake ghe-export-mysql data" "$TRASHDIR/restore-out"
  fi
  grep -q "fake ghe-export-redis data" "$TRASHDIR/restore-out"
  grep -q "fake ghe-export-authorized-keys data" "$TRASHDIR/restore-out"

  # tests that differ for cluster and single node backups
  if [ "$(cat $GHE_DATA_DIR/current/strategy)" = "rsync" ]; then
    grep -q "fake ghe-export-ssh-host-keys data" "$TRASHDIR/restore-out"
    # verify all ES data was transferred from live directory to the temporary restore directory
    diff -ru --exclude="*.gz" "$GHE_DATA_DIR/current/elasticsearch" "$GHE_REMOTE_DATA_USER_DIR/elasticsearch-restore"
  else
    grep -q "fake audit log last yr last mth" "$TRASHDIR/restore-out"
    grep -q "fake audit log this yr this mth" "$TRASHDIR/restore-out"
  fi

  # verify settings import was *not* run due to instance already being
  # configured.
  ! grep -q "fake ghe-export-settings data" "$TRASHDIR/restore-out"

  # verify management console password was *not* restored
  ! grep -q "fake password hash data" "$GHE_REMOTE_DATA_USER_DIR/common/secrets.conf"

  # verify management console argon2 secret was *not* restored
  ! grep -q "fake argon2 secret" "$GHE_REMOTE_DATA_USER_DIR/common/secrets.conf"

  # verify common data
  verify_common_data
}

subtract_minute() {
  # Expect date string in the format of yyyymmddTHHMMSS
  # Here parse date differently depending on GNU Linux vs BSD MacOS
  if date -v -1d > /dev/null 2>&1; then
    date -v -"$2"M -ujf'%Y%m%dT%H%M%S' "$1" +%Y%m%dT%H%M%S
  else
    dt=$1
    date '+%Y%m%dT%H%M%S' -d "${dt:0:8} ${dt:9:2}:${dt:11:2}:${dt:13:2} $2 minutes ago"
  fi
}

setup_mssql_backup_file() {
  rm -rf "$GHE_DATA_DIR/current/mssql"
  mkdir -p "$GHE_DATA_DIR/current/mssql"

  add_mssql_backup_file "$@"

  # Simulate ghe-export-mssql behavior
  if [ "$3" = "bak" ] || [ "$3" = "diff" ]; then
    touch "$GHE_DATA_DIR/current/mssql/$1@$fake_last_utc.log"
  fi
}

setup_mssql_stubs() {
  export REMOTE_DBS="full_mssql"

  # Transaction log LSN checks
  export NEXT_LOG_BACKUP_STARTING_LSN=100
  export LOG_BACKUP_FILE_LAST_LSN=100

  # Differential backup LSN checks
  export DIFFERENTIAL_BASE_LSN=100
  export FULL_BACKUP_FILE_LSN=100
}

add_mssql_backup_file() {
  # $1 name: <name>@...
  # $2 minutes ago
  # $3 extension: bak, diff, log
  current_utc=$(date -u +%Y%m%dT%H%M%S)
  fake_last_utc=$(subtract_minute "$current_utc" "$2")

  touch "$GHE_DATA_DIR/current/mssql/$1@$fake_last_utc.$3"
}

enable_actions() {
  ghe-ssh "$GHE_HOSTNAME" -- 'ghe-config app.actions.enabled true'
}

is_actions_enabled() {
  ghe-ssh "$GHE_HOSTNAME" -- 'ghe-config --true app.actions.enabled'
}

enable_minio() {
  ghe-ssh "$GHE_HOSTNAME" -- 'ghe-config app.minio.enabled true'
}

is_minio_enabled() {
  ghe-ssh "$GHE_HOSTNAME" -- 'ghe-config --true app.minio.enabled'
}

enable_binary_backup() {
  ghe-ssh "$GHE_HOSTNAME" -- 'ghe-config mysql.backup.binary true'
}

setup_moreutils_parallel() {
  # CI servers may have moreutils parallel and GNU parallel installed.
  # We need moreutils parallel
  local x
  for x in \
      /usr/bin/parallel-moreutils \
      /usr/bin/parallel.moreutils \
      /usr/bin/parallel_moreutils \
      /usr/bin/moreutils-parallel \
      /usr/bin/moreutils.parallel \
      /usr/bin/moreutils_parallel \
      ; do
        if [ -x "${x}" ]; then
            ln -sf "${x}" "$ROOTDIR/test/bin/parallel"
            break
        fi
  done
}

cleanup_moreutils_parallel() {
  if [ -h "$ROOTDIR/test/bin/parallel" ]; then
    unlink "$ROOTDIR/test/bin/parallel"
  fi
}

# setup_actions_enabled_in_settings_json writes settings for the Actions app to settings.json
# it accepts true or false as first argument to enable or disable actions in settings.json
setup_actions_enabled_settings_for_restore() {
  # Empty the file, it now contains "fake ghe-export-settings data"
  echo > "$GHE_DATA_DIR/1/settings.json"
  git config -f "$GHE_DATA_DIR/1/settings.json" --bool app.actions.enabled $1
}
