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:
@@ -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=""
|
||||||
|
|||||||
@@ -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"
|
||||||
@@ -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"
|
||||||
|
|||||||
Reference in New Issue
Block a user