#!/usr/bin/env bash # gniza tests — utility functions SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" BASE_DIR="$(dirname "$SCRIPT_DIR")" source "$BASE_DIR/lib/constants.sh" source "$BASE_DIR/lib/utils.sh" source "$BASE_DIR/lib/logging.sh" source "$BASE_DIR/lib/config.sh" source "$BASE_DIR/lib/accounts.sh" TESTS_RUN=0 TESTS_PASSED=0 TESTS_FAILED=0 assert_eq() { local expected="$1" local actual="$2" local msg="${3:-assertion}" ((TESTS_RUN++)) if [[ "$expected" == "$actual" ]]; then ((TESTS_PASSED++)) echo " ${C_GREEN}PASS${C_RESET}: $msg" else ((TESTS_FAILED++)) echo " ${C_RED}FAIL${C_RESET}: $msg" echo " expected: '$expected'" echo " actual: '$actual'" fi } assert_ok() { local msg="${1:-assertion}" ((TESTS_RUN++)) ((TESTS_PASSED++)) echo " ${C_GREEN}PASS${C_RESET}: $msg" } assert_fail() { local msg="${1:-assertion}" ((TESTS_RUN++)) ((TESTS_FAILED++)) echo " ${C_RED}FAIL${C_RESET}: $msg" } print_summary() { echo "" echo "==============================" echo "Tests: $TESTS_RUN | Passed: $TESTS_PASSED | Failed: $TESTS_FAILED" echo "==============================" (( TESTS_FAILED > 0 )) && exit 1 exit 0 } # ── Tests: utils.sh ─────────────────────────────────────────── echo "Testing utils.sh..." # timestamp format ts=$(timestamp) if [[ "$ts" =~ ^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{6}$ ]]; then assert_ok "timestamp format matches YYYY-MM-DDTHHMMSS" else assert_fail "timestamp format: got '$ts'" fi # human_size assert_eq "500 B" "$(human_size 500)" "human_size 500B" assert_eq "1.0 KB" "$(human_size 1024)" "human_size 1KB" assert_eq "1.0 MB" "$(human_size 1048576)" "human_size 1MB" assert_eq "1.0 GB" "$(human_size 1073741824)" "human_size 1GB" assert_eq "2.5 GB" "$(human_size 2684354560)" "human_size 2.5GB" assert_eq "5.3 KB" "$(human_size 5500)" "human_size 5.3KB" # human_duration assert_eq "5s" "$(human_duration 5)" "human_duration 5s" assert_eq "2m 30s" "$(human_duration 150)" "human_duration 2m30s" assert_eq "1h 5m 30s" "$(human_duration 3930)" "human_duration 1h5m30s" # require_cmd if require_cmd bash 2>/dev/null; then assert_ok "require_cmd bash" else assert_fail "require_cmd bash" fi # ── Tests: accounts.sh filter ───────────────────────────────── echo "" echo "Testing accounts.sh filter_accounts..." # filter with exclusions EXCLUDE_ACCOUNTS="nobody,system" INCLUDE_ACCOUNTS="" result=$(filter_accounts $'alice\nbob\nnobody\nsystem\ncharlie') assert_eq $'alice\nbob\ncharlie' "$result" "filter excludes nobody,system" # filter with inclusions INCLUDE_ACCOUNTS="alice,bob" EXCLUDE_ACCOUNTS="nobody" result=$(filter_accounts $'alice\nbob\nnobody\ncharlie') assert_eq $'alice\nbob' "$result" "filter includes only alice,bob" # filter with both include and exclude INCLUDE_ACCOUNTS="alice,bob,nobody" EXCLUDE_ACCOUNTS="nobody" result=$(filter_accounts $'alice\nbob\nnobody') assert_eq $'alice\nbob' "$result" "filter include+exclude: nobody excluded from include list" # ── Tests: config.sh validation ─────────────────────────────── echo "" echo "Testing config.sh validation..." # Suppress log output for validation tests LOG_FILE="/dev/null" # Create a temp file to use as fake SSH key _test_key=$(mktemp) trap 'rm -f "$_test_key"' EXIT # Test validation with invalid LOG_LEVEL NOTIFY_ON="failure" LOG_LEVEL="invalid" if validate_config 2>/dev/null; then assert_fail "validate_config should fail with invalid LOG_LEVEL" else assert_ok "validate_config catches invalid LOG_LEVEL" fi # Test validation with valid config (REMOTE_* not validated here — per-remote only) NOTIFY_ON="failure" LOG_LEVEL="info" if validate_config 2>/dev/null; then assert_ok "validate_config passes with valid config" else assert_fail "validate_config should pass with valid config" fi # Test invalid NOTIFY_ON NOTIFY_ON="invalid" if validate_config 2>/dev/null; then assert_fail "validate_config should fail with invalid NOTIFY_ON" else assert_ok "validate_config catches invalid NOTIFY_ON" fi # ── Tests: validate_timestamp ──────────────────────────────── echo "" echo "Testing validate_timestamp..." if validate_timestamp "2024-01-15T023000" 2>/dev/null; then assert_ok "validate_timestamp accepts valid format" else assert_fail "validate_timestamp should accept 2024-01-15T023000" fi if validate_timestamp "2024-12-31T235959" 2>/dev/null; then assert_ok "validate_timestamp accepts end-of-year" else assert_fail "validate_timestamp should accept 2024-12-31T235959" fi if validate_timestamp "not-a-timestamp" 2>/dev/null; then assert_fail "validate_timestamp should reject garbage" else assert_ok "validate_timestamp rejects garbage input" fi if validate_timestamp "2024-01-15 02:30:00" 2>/dev/null; then assert_fail "validate_timestamp should reject spaces/colons" else assert_ok "validate_timestamp rejects spaces/colons" fi if validate_timestamp "" 2>/dev/null; then assert_fail "validate_timestamp should reject empty string" else assert_ok "validate_timestamp rejects empty string" fi # ── Tests: validate_account_name ───────────────────────────── echo "" echo "Testing validate_account_name..." if validate_account_name "alice" 2>/dev/null; then assert_ok "validate_account_name accepts 'alice'" else assert_fail "validate_account_name should accept 'alice'" fi if validate_account_name "user123" 2>/dev/null; then assert_ok "validate_account_name accepts 'user123'" else assert_fail "validate_account_name should accept 'user123'" fi if validate_account_name "my-site" 2>/dev/null; then assert_ok "validate_account_name accepts 'my-site'" else assert_fail "validate_account_name should accept 'my-site'" fi if validate_account_name "Root" 2>/dev/null; then assert_fail "validate_account_name should reject uppercase" else assert_ok "validate_account_name rejects uppercase" fi if validate_account_name "123user" 2>/dev/null; then assert_fail "validate_account_name should reject leading digit" else assert_ok "validate_account_name rejects leading digit" fi if validate_account_name "../etc/passwd" 2>/dev/null; then assert_fail "validate_account_name should reject path traversal" else assert_ok "validate_account_name rejects path traversal" fi if validate_account_name "" 2>/dev/null; then assert_fail "validate_account_name should reject empty" else assert_ok "validate_account_name rejects empty string" fi if validate_account_name "a]b" 2>/dev/null; then assert_fail "validate_account_name should reject special chars" else assert_ok "validate_account_name rejects special chars" fi # ── Tests: _safe_source_config ─────────────────────────────── echo "" echo "Testing _safe_source_config..." _test_conf=$(mktemp) cat > "$_test_conf" <<'CONF' # Comment line MYKEY="hello world" ANOTHER='single quoted' BARE=noquotes # blank lines above NUMERIC=42 CONF # Clear any previous values unset MYKEY ANOTHER BARE NUMERIC 2>/dev/null || true _safe_source_config "$_test_conf" assert_eq "hello world" "$MYKEY" "_safe_source_config reads double-quoted value" assert_eq "single quoted" "$ANOTHER" "_safe_source_config reads single-quoted value" assert_eq "noquotes" "$BARE" "_safe_source_config reads bare value" assert_eq "42" "$NUMERIC" "_safe_source_config reads numeric value" # Test that malicious content is not executed _test_conf2=$(mktemp) cat > "$_test_conf2" <<'CONF' SAFE_KEY="safe" $(echo pwned) `rm -rf /` CONF unset SAFE_KEY 2>/dev/null || true _safe_source_config "$_test_conf2" assert_eq "safe" "$SAFE_KEY" "_safe_source_config reads safe key from malicious file" rm -f "$_test_conf" "$_test_conf2" # ── Tests: config.sh validation (new fields) ──────────────── echo "" echo "Testing config.sh validation (numeric + RSYNC_EXTRA_OPTS)..." # Valid config baseline NOTIFY_ON="failure" LOG_LEVEL="info" SMTP_HOST="" SSH_TIMEOUT="30" SSH_RETRIES="3" LOG_RETAIN="90" RSYNC_EXTRA_OPTS="" if validate_config 2>/dev/null; then assert_ok "validate_config passes with valid numeric fields" else assert_fail "validate_config should pass with valid config" fi # Invalid SSH_TIMEOUT SSH_TIMEOUT="abc" if validate_config 2>/dev/null; then assert_fail "validate_config should fail with non-numeric SSH_TIMEOUT" else assert_ok "validate_config catches non-numeric SSH_TIMEOUT" fi SSH_TIMEOUT="30" # Invalid SSH_RETRIES SSH_RETRIES="abc" if validate_config 2>/dev/null; then assert_fail "validate_config should fail with non-numeric SSH_RETRIES" else assert_ok "validate_config catches non-numeric SSH_RETRIES" fi SSH_RETRIES="3" # Invalid RSYNC_EXTRA_OPTS (shell metacharacters) RSYNC_EXTRA_OPTS='--rsh="evil command"' if validate_config 2>/dev/null; then assert_fail "validate_config should fail with dangerous RSYNC_EXTRA_OPTS" else assert_ok "validate_config catches dangerous RSYNC_EXTRA_OPTS" fi # Valid RSYNC_EXTRA_OPTS RSYNC_EXTRA_OPTS="--compress --bwlimit=1000" if validate_config 2>/dev/null; then assert_ok "validate_config accepts valid RSYNC_EXTRA_OPTS" else assert_fail "validate_config should accept --compress --bwlimit=1000" fi RSYNC_EXTRA_OPTS="" print_summary