f472a7abfc
alt_mysql_restore_payload.sh (the DirectAdmin native-restore hook that imports a plugin-origin backup payload into alt-mariadb) had zero behavioral test coverage - only hook-installation plumbing was tested. Add a fake mariadb/mariadb-dump client stub (tracking a simple existing-databases state file) to exercise the real script end-to-end without a live server, covering: checksum mismatch rejection, overwrite=deny enforcement against an existing database, and the happy-path restore. Add a DA_ALT_MYSQL_SETTINGS_FILE override (same pattern as worker.sh's PLUGIN_DIR) so the script's settings file can be pointed at a test fixture instead of the real system one. Also extend da_integration_install_test.sh to check hook-symlink install/uninstall coverage for database_delete_pre.sh, database_user_password_change_pre.sh, database_user_create_post.sh, and database_destroy_user_post.sh, which were previously untested (only database_create_pre.sh was checked).
160 lines
5.0 KiB
Bash
160 lines
5.0 KiB
Bash
#!/bin/bash
|
|
set -euo pipefail
|
|
|
|
PLUGIN_DIR="$(cd "$(dirname "$0")/.." && pwd)"
|
|
SCRIPT="$PLUGIN_DIR/scripts/da-integration/alt_mysql_restore_payload.sh"
|
|
TMP_DIR="$(mktemp -d)"
|
|
trap 'rm -rf "$TMP_DIR"' EXIT
|
|
|
|
fail() {
|
|
echo "FAIL: $*" >&2
|
|
exit 1
|
|
}
|
|
|
|
sha256_of() {
|
|
if command -v sha256sum >/dev/null 2>&1; then
|
|
sha256sum "$1" | awk '{print $1}'
|
|
else
|
|
shasum -a 256 "$1" | awk '{print $1}'
|
|
fi
|
|
}
|
|
|
|
# --- Fake alt-mariadb client binaries + credentials, isolated from the real system ---
|
|
# The stub tracks a simple "existing databases" state file so CREATE/DROP
|
|
# DATABASE during the test actually affects what a later existence check sees.
|
|
mkdir -p "$TMP_DIR/mariadb/bin"
|
|
cat > "$TMP_DIR/mariadb/bin/mariadb" <<'STUB'
|
|
#!/bin/bash
|
|
STATE_FILE="${FAKE_DB_STATE_FILE:?FAKE_DB_STATE_FILE must be set}"
|
|
|
|
sql=""
|
|
mode=""
|
|
for arg in "$@"; do
|
|
case "$arg" in
|
|
-e) mode="sql" ;;
|
|
-Nse) mode="sql" ;;
|
|
--database=*|--user=*|--socket=*|--host=*|--port=*|--protocol=*) ;;
|
|
*)
|
|
if [ "$mode" = "sql" ]; then
|
|
sql="$arg"
|
|
mode=""
|
|
fi
|
|
;;
|
|
esac
|
|
done
|
|
|
|
if [ -n "$sql" ]; then
|
|
case "$sql" in
|
|
*"SELECT 1 FROM information_schema.SCHEMATA"*)
|
|
name="$(printf '%s' "$sql" | sed -n "s/.*SCHEMA_NAME='\([^']*\)'.*/\1/p")"
|
|
grep -qx "$name" "$STATE_FILE" 2>/dev/null && echo 1
|
|
;;
|
|
*"DROP DATABASE"*)
|
|
name="$(printf '%s' "$sql" | sed -n 's/.*DATABASE[^`]*`\([A-Za-z0-9_]*\)`.*/\1/p')"
|
|
if [ -n "$name" ]; then
|
|
grep -vx "$name" "$STATE_FILE" > "$STATE_FILE.tmp" 2>/dev/null || true
|
|
mv -f "$STATE_FILE.tmp" "$STATE_FILE"
|
|
fi
|
|
;;
|
|
*"CREATE DATABASE"*)
|
|
name="$(printf '%s' "$sql" | sed -n 's/.*DATABASE[^`]*`\([A-Za-z0-9_]*\)`.*/\1/p')"
|
|
[ -n "$name" ] && echo "$name" >> "$STATE_FILE"
|
|
;;
|
|
esac
|
|
exit 0
|
|
fi
|
|
|
|
# No -e/-Nse: reading an import from stdin
|
|
cat >/dev/null
|
|
if [ "${FAKE_MYSQL_IMPORT_FAIL:-0}" = "1" ]; then
|
|
echo "fake mariadb: simulated import failure" >&2
|
|
exit 1
|
|
fi
|
|
exit 0
|
|
STUB
|
|
chmod 755 "$TMP_DIR/mariadb/bin/mariadb"
|
|
|
|
cat > "$TMP_DIR/mariadb/bin/mariadb-dump" <<'STUB'
|
|
#!/bin/bash
|
|
echo "-- fake rollback dump"
|
|
echo "SELECT 1;"
|
|
exit 0
|
|
STUB
|
|
chmod 755 "$TMP_DIR/mariadb/bin/mariadb-dump"
|
|
|
|
cat > "$TMP_DIR/alt-mysql.conf" <<EOF
|
|
host=
|
|
passwd=secret
|
|
port=4407
|
|
socket=$TMP_DIR/data/mariadb.sock
|
|
user=da_admin
|
|
EOF
|
|
|
|
cat > "$TMP_DIR/settings.conf" <<EOF
|
|
MYSQL_PLUGIN_CREDENTIALS_FILE=$TMP_DIR/alt-mysql.conf
|
|
MYSQL_PLUGIN_CLIENT_BIN_DIR=$TMP_DIR/mariadb/bin
|
|
EOF
|
|
|
|
# --- Build a valid manifest + payload files ---
|
|
build_payload() {
|
|
local payload_dir="$1" db_name="$2"
|
|
mkdir -p "$payload_dir/databases" "$payload_dir/grants" "$payload_dir/metadata"
|
|
printf 'CREATE TABLE t (id INT);\n' > "$payload_dir/databases/${db_name}.sql"
|
|
gzip -9 -f "$payload_dir/databases/${db_name}.sql"
|
|
printf '%s\n' '-- users' | gzip -9 > "$payload_dir/grants/users.sql.gz"
|
|
printf '%s\n' '-- grants' | gzip -9 > "$payload_dir/grants/grants.sql.gz"
|
|
local sha
|
|
sha="$(sha256_of "$payload_dir/databases/${db_name}.sql.gz")"
|
|
cat > "$payload_dir/manifest.json" <<JSON
|
|
{
|
|
"format": "da-alt-mysql-v1",
|
|
"created_at": "2026-01-01T00:00:00Z",
|
|
"da_user": "demo",
|
|
"databases": [
|
|
{ "name": "${db_name}", "file": "databases/${db_name}.sql.gz", "sha256": "${sha}" }
|
|
]
|
|
}
|
|
JSON
|
|
}
|
|
|
|
run_restore() {
|
|
local db_state
|
|
db_state="$(mktemp)"
|
|
printf '%s\n' "${FAKE_EXISTING_DBS:-}" | tr ',' '\n' | sed '/^$/d' > "$db_state"
|
|
|
|
DA_ALT_MYSQL_SETTINGS_FILE="$TMP_DIR/settings.conf" \
|
|
DA_ALT_MYSQL_RESTORE_LOG="$TMP_DIR/restore.log" \
|
|
FAKE_DB_STATE_FILE="$db_state" \
|
|
bash "$SCRIPT" "$@"
|
|
}
|
|
|
|
# --- Scenario 1: checksum mismatch must be rejected before any restore happens ---
|
|
PAYLOAD1="$TMP_DIR/payload1"
|
|
build_payload "$PAYLOAD1" "demo_app"
|
|
echo 'CORRUPTED CONTENT' > "$PAYLOAD1/databases/demo_app.sql.gz"
|
|
|
|
OUTPUT1="$(run_restore --user demo --payload "$PAYLOAD1" --overwrite allow 2>&1)" && fail "checksum mismatch must cause a non-zero exit, output: $OUTPUT1"
|
|
echo "$OUTPUT1" | grep -qi "checksum mismatch" \
|
|
|| fail "expected a checksum mismatch error, got: $OUTPUT1"
|
|
[ ! -f "$TMP_DIR/restore.log" ] || ! grep -qi "restoring user definitions" "$TMP_DIR/restore.log" \
|
|
|| fail "restore must not proceed past checksum verification on mismatch"
|
|
|
|
# --- Scenario 2: overwrite=deny must refuse to touch an existing database ---
|
|
PAYLOAD2="$TMP_DIR/payload2"
|
|
build_payload "$PAYLOAD2" "demo_existing"
|
|
|
|
OUTPUT2="$(FAKE_EXISTING_DBS="demo_existing" run_restore --user demo --payload "$PAYLOAD2" --overwrite deny 2>&1)" && fail "overwrite=deny must reject an existing database, output: $OUTPUT2"
|
|
echo "$OUTPUT2" | grep -qi "overwrite is denied" \
|
|
|| fail "expected an overwrite-denied error, got: $OUTPUT2"
|
|
|
|
# --- Scenario 3: happy path - valid checksum, no conflict, overwrite=allow ---
|
|
PAYLOAD3="$TMP_DIR/payload3"
|
|
build_payload "$PAYLOAD3" "demo_new"
|
|
|
|
run_restore --user demo --payload "$PAYLOAD3" --overwrite allow >/dev/null 2>&1 \
|
|
|| fail "a valid payload with no conflicts and overwrite=allow should succeed"
|
|
grep -qi "restore payload completed" "$TMP_DIR/restore.log" \
|
|
|| fail "expected the restore to log completion"
|
|
|
|
echo "alt_mysql_restore_payload_test: OK"
|