From 1e73c29e37fc874beca47169cf5d03c233f54b61 Mon Sep 17 00:00:00 2001 From: Renz Date: Tue, 25 Nov 2025 17:33:41 +0000 Subject: [PATCH] fix: Ensure CLI flags have priority over config file - CLI flags were being overwritten by .dbbackup.conf values - Implemented flag tracking using cmd.Flags().Visit() - Explicit flags now preserved after config loading - Fixes backup-dir, host, port, compression, and other flags - All backup files (.dump, .sha256, .info) now created correctly Also fixed QA test issues: - grep -q was closing pipe early, killing backup before completion - Fixed glob patterns in test assertions - Corrected config file field names (backup_dir not dir) QA Results: 22/24 tests pass (92%), 0 CRITICAL issues Remaining 2 failures are TUI tests requiring expect/pexpect --- cmd/root.go | 52 ++++++++ run_qa_tests.sh | 315 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 367 insertions(+) create mode 100755 run_qa_tests.sh diff --git a/cmd/root.go b/cmd/root.go index d62fe59..86d0460 100755 --- a/cmd/root.go +++ b/cmd/root.go @@ -8,6 +8,7 @@ import ( "dbbackup/internal/logger" "dbbackup/internal/security" "github.com/spf13/cobra" + "github.com/spf13/pflag" ) var ( @@ -42,13 +43,64 @@ For help with specific commands, use: dbbackup [command] --help`, return nil } + // Store which flags were explicitly set by user + flagsSet := make(map[string]bool) + cmd.Flags().Visit(func(f *pflag.Flag) { + flagsSet[f.Name] = true + }) + // Load local config if not disabled if !cfg.NoLoadConfig { if localCfg, err := config.LoadLocalConfig(); err != nil { log.Warn("Failed to load local config", "error", err) } else if localCfg != nil { + // Save current flag values that were explicitly set + savedBackupDir := cfg.BackupDir + savedHost := cfg.Host + savedPort := cfg.Port + savedUser := cfg.User + savedDatabase := cfg.Database + savedCompression := cfg.CompressionLevel + savedJobs := cfg.Jobs + savedDumpJobs := cfg.DumpJobs + savedRetentionDays := cfg.RetentionDays + savedMinBackups := cfg.MinBackups + + // Apply config from file config.ApplyLocalConfig(cfg, localCfg) log.Info("Loaded configuration from .dbbackup.conf") + + // Restore explicitly set flag values (flags have priority) + if flagsSet["backup-dir"] { + cfg.BackupDir = savedBackupDir + } + if flagsSet["host"] { + cfg.Host = savedHost + } + if flagsSet["port"] { + cfg.Port = savedPort + } + if flagsSet["user"] { + cfg.User = savedUser + } + if flagsSet["database"] { + cfg.Database = savedDatabase + } + if flagsSet["compression"] { + cfg.CompressionLevel = savedCompression + } + if flagsSet["jobs"] { + cfg.Jobs = savedJobs + } + if flagsSet["dump-jobs"] { + cfg.DumpJobs = savedDumpJobs + } + if flagsSet["retention-days"] { + cfg.RetentionDays = savedRetentionDays + } + if flagsSet["min-backups"] { + cfg.MinBackups = savedMinBackups + } } } diff --git a/run_qa_tests.sh b/run_qa_tests.sh new file mode 100755 index 0000000..cd606dd --- /dev/null +++ b/run_qa_tests.sh @@ -0,0 +1,315 @@ +#!/bin/bash +# +# Automated QA Test Script for dbbackup Interactive Mode +# Tests as many features as possible without manual interaction +# + +set -e + +# Colors +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +CYAN='\033[0;36m' +NC='\033[0m' + +# Config +BINARY="/root/dbbackup/dbbackup" +TEST_DIR="/tmp/dbbackup_qa_test" +BACKUP_DIR="$TEST_DIR/backups" +LOG_FILE="$TEST_DIR/qa_test_$(date +%Y%m%d_%H%M%S).log" +REPORT_FILE="/root/dbbackup/QA_TEST_RESULTS.md" + +# Counters +TOTAL_TESTS=0 +PASSED_TESTS=0 +FAILED_TESTS=0 +SKIPPED_TESTS=0 +CRITICAL_ISSUES=0 +MAJOR_ISSUES=0 +MINOR_ISSUES=0 + +echo -e "${CYAN}╔════════════════════════════════════════════════════════════════╗${NC}" +echo -e "${CYAN}║ QA Test Suite - dbbackup Interactive Mode ║${NC}" +echo -e "${CYAN}╔════════════════════════════════════════════════════════════════╗${NC}" +echo +echo -e "${BLUE}Test Date:${NC} $(date)" +echo -e "${BLUE}Environment:${NC} $(uname -s) $(uname -m)" +echo -e "${BLUE}Binary:${NC} $BINARY" +echo -e "${BLUE}Test Directory:${NC} $TEST_DIR" +echo -e "${BLUE}Log File:${NC} $LOG_FILE" +echo + +# Check if running as root +if [ "$(id -u)" -ne 0 ]; then + echo -e "${RED}ERROR: Must run as root for postgres user switching${NC}" + exit 1 +fi + +# Setup +echo -e "${YELLOW}► Setting up test environment...${NC}" +rm -rf "$TEST_DIR" +mkdir -p "$BACKUP_DIR" +chmod 755 "$TEST_DIR" "$BACKUP_DIR" +chown -R postgres:postgres "$TEST_DIR" +cp "$BINARY" "$TEST_DIR/" +chmod 755 "$TEST_DIR/dbbackup" +chown postgres:postgres "$TEST_DIR/dbbackup" +echo -e "${GREEN}✓ Environment ready${NC}" +echo + +# Test function +run_test() { + local name="$1" + local severity="$2" # CRITICAL, MAJOR, MINOR + local cmd="$3" + + TOTAL_TESTS=$((TOTAL_TESTS + 1)) + echo -e "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" + echo -e "${CYAN}TEST $TOTAL_TESTS: $name${NC}" + echo -e "${CYAN}Severity: $severity${NC}" + echo -e "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" + + if [ -n "$cmd" ]; then + echo -e "${YELLOW}Command:${NC} $cmd" + echo + + if eval "$cmd" >> "$LOG_FILE" 2>&1; then + echo -e "${GREEN}✅ PASSED${NC}" + PASSED_TESTS=$((PASSED_TESTS + 1)) + else + echo -e "${RED}❌ FAILED${NC}" + FAILED_TESTS=$((FAILED_TESTS + 1)) + + case "$severity" in + CRITICAL) CRITICAL_ISSUES=$((CRITICAL_ISSUES + 1)) ;; + MAJOR) MAJOR_ISSUES=$((MAJOR_ISSUES + 1)) ;; + MINOR) MINOR_ISSUES=$((MINOR_ISSUES + 1)) ;; + esac + fi + else + echo -e "${YELLOW}⏭️ MANUAL TEST REQUIRED${NC}" + SKIPPED_TESTS=$((SKIPPED_TESTS + 1)) + fi + + echo +} + +cd "$TEST_DIR" + +# ============================================================================ +# PHASE 1: Basic Functionality (CRITICAL) +# ============================================================================ + +echo -e "${BLUE}╔════════════════════════════════════════════════════════════════╗${NC}" +echo -e "${BLUE}║ PHASE 1: Basic Functionality (CRITICAL) ║${NC}" +echo -e "${BLUE}╚════════════════════════════════════════════════════════════════╝${NC}" +echo + +run_test "Application Version Check" "CRITICAL" \ + "su - postgres -c 'cd $TEST_DIR && ./dbbackup --version'" + +run_test "Application Help" "CRITICAL" \ + "su - postgres -c 'cd $TEST_DIR && ./dbbackup --help' | grep -q 'interactive'" + +run_test "Interactive Mode Launch (--help)" "CRITICAL" \ + "su - postgres -c 'cd $TEST_DIR && ./dbbackup interactive --help' | grep -q 'auto-select'" + +run_test "Single Database Backup (CLI)" "CRITICAL" \ + "su - postgres -c 'cd $TEST_DIR && ./dbbackup backup single postgres --backup-dir $BACKUP_DIR' > /dev/null 2>&1" + +run_test "Verify Backup Files Created" "CRITICAL" \ + "ls $BACKUP_DIR/db_postgres_*.dump >/dev/null 2>&1 && ls $BACKUP_DIR/db_postgres_*.dump.sha256 >/dev/null 2>&1" + +run_test "Backup Checksum Validation" "CRITICAL" \ + "cd $BACKUP_DIR && sha256sum -c \$(ls -t db_postgres_*.sha256 | head -1) 2>&1 | grep -q 'OK'" + +run_test "List Backups Command" "CRITICAL" \ + "su - postgres -c 'cd $TEST_DIR && ./dbbackup list' | grep -q 'backup'" + +# ============================================================================ +# PHASE 2: TUI Auto-Select Tests (MAJOR) +# ============================================================================ + +echo -e "${BLUE}╔════════════════════════════════════════════════════════════════╗${NC}" +echo -e "${BLUE}║ PHASE 2: TUI Automation (MAJOR) ║${NC}" +echo -e "${BLUE}╚════════════════════════════════════════════════════════════════╝${NC}" +echo + +run_test "TUI Auto-Select Single Backup" "MAJOR" \ + "timeout 5s su - postgres -c 'cd $TEST_DIR && ./dbbackup interactive --auto-select 0 --auto-database postgres --debug' 2>&1 | grep -q 'Auto-select'" + +run_test "TUI Auto-Select Status View" "MAJOR" \ + "timeout 3s su - postgres -c 'cd $TEST_DIR && ./dbbackup interactive --auto-select 10 --debug' 2>&1 | grep -q 'Status\|Database'" + +run_test "TUI Auto-Select with Logging" "MAJOR" \ + "timeout 3s su - postgres -c 'cd $TEST_DIR && ./dbbackup interactive --auto-select 10 --verbose-tui --tui-log-file $TEST_DIR/tui.log' 2>&1 && test -f $TEST_DIR/tui.log" + +# ============================================================================ +# PHASE 3: Configuration Tests (MAJOR) +# ============================================================================ + +echo -e "${BLUE}╔════════════════════════════════════════════════════════════════╗${NC}" +echo -e "${BLUE}║ PHASE 3: Configuration (MAJOR) ║${NC}" +echo -e "${BLUE}╚════════════════════════════════════════════════════════════════╝${NC}" +echo + +# Create test config +cat > "$TEST_DIR/.dbbackup.conf" <&1 | grep -q 'Loaded configuration'" + +run_test "Config File Created After Backup" "MAJOR" \ + "test -f $TEST_DIR/.dbbackup.conf && grep -q 'retention_days' $TEST_DIR/.dbbackup.conf" + +run_test "Config File No Password Leak" "CRITICAL" \ + "! grep -i 'password.*=' $TEST_DIR/.dbbackup.conf" + +# ============================================================================ +# PHASE 4: Security Features (CRITICAL) +# ============================================================================ + +echo -e "${BLUE}╔════════════════════════════════════════════════════════════════╗${NC}" +echo -e "${BLUE}║ PHASE 4: Security Features (CRITICAL) ║${NC}" +echo -e "${BLUE}╚════════════════════════════════════════════════════════════════╝${NC}" +echo + +run_test "Retention Policy Flag Available" "MAJOR" \ + "su - postgres -c 'cd $TEST_DIR && ./dbbackup --help' | grep -q 'retention-days'" + +run_test "Rate Limiting Flag Available" "MAJOR" \ + "su - postgres -c 'cd $TEST_DIR && ./dbbackup --help' | grep -q 'max-retries'" + +run_test "Privilege Check Flag Available" "MAJOR" \ + "su - postgres -c 'cd $TEST_DIR && ./dbbackup --help' | grep -q 'allow-root'" + +run_test "Resource Check Flag Available" "MAJOR" \ + "su - postgres -c 'cd $TEST_DIR && ./dbbackup --help' | grep -q 'check-resources'" + +# Create old backups for retention test +su - postgres -c " +cd $BACKUP_DIR +touch -d '40 days ago' db_old_40.dump db_old_40.dump.sha256 db_old_40.dump.info +touch -d '35 days ago' db_old_35.dump db_old_35.dump.sha256 db_old_35.dump.info +" + +run_test "Retention Policy Cleanup" "MAJOR" \ + "su - postgres -c 'cd $TEST_DIR && ./dbbackup backup single postgres --retention-days 30 --min-backups 2 --debug' 2>&1 | grep -q 'Removing old backup' && ! test -f $BACKUP_DIR/db_old_40.dump" + +# ============================================================================ +# PHASE 5: Error Handling (MAJOR) +# ============================================================================ + +echo -e "${BLUE}╔════════════════════════════════════════════════════════════════╗${NC}" +echo -e "${BLUE}║ PHASE 5: Error Handling (MAJOR) ║${NC}" +echo -e "${BLUE}╚════════════════════════════════════════════════════════════════╝${NC}" +echo + +run_test "Invalid Database Name Handling" "MAJOR" \ + "su - postgres -c 'cd $TEST_DIR && ./dbbackup backup single nonexistent_db_xyz_123' 2>&1 | grep -qE 'error|failed|not found'" + +run_test "Invalid Host Handling" "MAJOR" \ + "su - postgres -c 'cd $TEST_DIR && ./dbbackup backup single postgres --host invalid.host.xyz --max-retries 1' 2>&1 | grep -qE 'connection.*failed|error'" + +run_test "Invalid Compression Level" "MINOR" \ + "su - postgres -c 'cd $TEST_DIR && ./dbbackup backup single postgres --compression 15' 2>&1 | grep -qE 'invalid|error'" + +# ============================================================================ +# PHASE 6: Data Integrity (CRITICAL) +# ============================================================================ + +echo -e "${BLUE}╔════════════════════════════════════════════════════════════════╗${NC}" +echo -e "${BLUE}║ PHASE 6: Data Integrity (CRITICAL) ║${NC}" +echo -e "${BLUE}╚════════════════════════════════════════════════════════════════╝${NC}" +echo + +run_test "Backup File is Valid PostgreSQL Dump" "CRITICAL" \ + "file $BACKUP_DIR/db_postgres_*.dump | grep -qE 'PostgreSQL|data'" + +run_test "Checksum File Format Valid" "CRITICAL" \ + "cat $BACKUP_DIR/db_postgres_*.sha256 | grep -qE '[0-9a-f]{64}'" + +run_test "Metadata File Created" "MAJOR" \ + "ls $BACKUP_DIR/db_postgres_*.dump.info >/dev/null 2>&1 && grep -q 'timestamp' $BACKUP_DIR/db_postgres_*.dump.info" + +# ============================================================================ +# Summary +# ============================================================================ + +echo +echo -e "${CYAN}╔════════════════════════════════════════════════════════════════╗${NC}" +echo -e "${CYAN}║ TEST SUMMARY ║${NC}" +echo -e "${CYAN}╚════════════════════════════════════════════════════════════════╝${NC}" +echo +echo -e "${BLUE}Total Tests:${NC} $TOTAL_TESTS" +echo -e "${GREEN}Passed:${NC} $PASSED_TESTS" +echo -e "${RED}Failed:${NC} $FAILED_TESTS" +echo -e "${YELLOW}Skipped:${NC} $SKIPPED_TESTS" +echo +echo -e "${BLUE}Issues by Severity:${NC}" +echo -e "${RED} Critical:${NC} $CRITICAL_ISSUES" +echo -e "${YELLOW} Major:${NC} $MAJOR_ISSUES" +echo -e "${YELLOW} Minor:${NC} $MINOR_ISSUES" +echo +echo -e "${BLUE}Log File:${NC} $LOG_FILE" +echo + +# Update report file +cat >> "$REPORT_FILE" <