Add behavioral test coverage for DA-native restore payload + hooks

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).
This commit is contained in:
Marek Miklewicz
2026-07-04 22:49:28 +02:00
parent 5ede413f52
commit f472a7abfc
3 changed files with 170 additions and 1 deletions
@@ -4,7 +4,7 @@ set -Eeuo pipefail
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
PLUGIN_DIR="$(cd "$SCRIPT_DIR/../.." && pwd)" PLUGIN_DIR="$(cd "$SCRIPT_DIR/../.." && pwd)"
COMMON_FILE="$PLUGIN_DIR/scripts/setup/mysql_common.sh" COMMON_FILE="$PLUGIN_DIR/scripts/setup/mysql_common.sh"
SETTINGS_FILE="$PLUGIN_DIR/plugin-settings.conf" SETTINGS_FILE="${DA_ALT_MYSQL_SETTINGS_FILE:-$PLUGIN_DIR/plugin-settings.conf}"
LOG_FILE="${DA_ALT_MYSQL_RESTORE_LOG:-$PLUGIN_DIR/data/logs/da-restore.log}" LOG_FILE="${DA_ALT_MYSQL_RESTORE_LOG:-$PLUGIN_DIR/data/logs/da-restore.log}"
DA_USER="" DA_USER=""
PAYLOAD_DIR="" PAYLOAD_DIR=""
+159
View File
@@ -0,0 +1,159 @@
#!/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"
+10
View File
@@ -27,9 +27,19 @@ grep -Fq "managed by DirectAdmin MySQL plugin hook dispatcher" "$TMP_DIR/custom/
[ -L "$TMP_DIR/custom/user_backup_compress_pre.sh.d/10-alt-mysql.sh" ] || fail "alt backup hook link missing" [ -L "$TMP_DIR/custom/user_backup_compress_pre.sh.d/10-alt-mysql.sh" ] || fail "alt backup hook link missing"
[ -L "$TMP_DIR/custom/user_restore_post_pre_cleanup.sh.d/10-alt-mysql.sh" ] || fail "alt restore hook link missing" [ -L "$TMP_DIR/custom/user_restore_post_pre_cleanup.sh.d/10-alt-mysql.sh" ] || fail "alt restore hook link missing"
[ -L "$TMP_DIR/custom/database_create_pre.sh.d/20-alt-mysql-block-native-db.sh" ] || fail "native DB blocker link missing" [ -L "$TMP_DIR/custom/database_create_pre.sh.d/20-alt-mysql-block-native-db.sh" ] || fail "native DB blocker link missing"
[ -L "$TMP_DIR/custom/database_delete_pre.sh.d/20-alt-mysql-block-native-db.sh" ] || fail "native DB delete blocker link missing"
[ -L "$TMP_DIR/custom/database_user_password_change_pre.sh.d/20-alt-mysql-block-native-db.sh" ] || fail "native DB password-change blocker link missing"
[ -L "$TMP_DIR/custom/database_user_create_post.sh.d/20-alt-mysql-observe-native-db.sh" ] || fail "native DB user-create observer link missing"
[ -L "$TMP_DIR/custom/database_destroy_user_post.sh.d/20-alt-mysql-observe-native-db.sh" ] || fail "native DB user-destroy observer link missing"
DA_CUSTOM_HOOK_DIR="$TMP_DIR/custom" bash "$INSTALLER" uninstall >/dev/null DA_CUSTOM_HOOK_DIR="$TMP_DIR/custom" bash "$INSTALLER" uninstall >/dev/null
[ ! -e "$TMP_DIR/custom/user_backup_compress_pre.sh.d/10-alt-mysql.sh" ] || fail "alt backup hook link not removed" [ ! -e "$TMP_DIR/custom/user_backup_compress_pre.sh.d/10-alt-mysql.sh" ] || fail "alt backup hook link not removed"
[ ! -e "$TMP_DIR/custom/user_restore_post_pre_cleanup.sh.d/10-alt-mysql.sh" ] || fail "alt restore hook link not removed"
[ ! -e "$TMP_DIR/custom/database_create_pre.sh.d/20-alt-mysql-block-native-db.sh" ] || fail "native DB blocker link not removed"
[ ! -e "$TMP_DIR/custom/database_delete_pre.sh.d/20-alt-mysql-block-native-db.sh" ] || fail "native DB delete blocker link not removed"
[ ! -e "$TMP_DIR/custom/database_user_password_change_pre.sh.d/20-alt-mysql-block-native-db.sh" ] || fail "native DB password-change blocker link not removed"
[ ! -e "$TMP_DIR/custom/database_user_create_post.sh.d/20-alt-mysql-observe-native-db.sh" ] || fail "native DB user-create observer link not removed"
[ ! -e "$TMP_DIR/custom/database_destroy_user_post.sh.d/20-alt-mysql-observe-native-db.sh" ] || fail "native DB user-destroy observer link not removed"
[ -f "$TMP_DIR/custom/user_backup_compress_pre.sh.d/00-existing.sh" ] || fail "preserved existing hook should remain" [ -f "$TMP_DIR/custom/user_backup_compress_pre.sh.d/00-existing.sh" ] || fail "preserved existing hook should remain"
echo "da_integration_install_test: OK" echo "da_integration_install_test: OK"