Files
shell/plex/test-plex-backup.sh
2025-05-29 11:25:02 -04:00

668 lines
18 KiB
Bash
Executable File

#!/bin/bash
# Comprehensive Plex Backup System Test Suite
# This script provides automated testing for all backup-related functionality
set -e
# Color codes for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
CYAN='\033[0;36m'
NC='\033[0m' # No Color
# Test configuration
SCRIPT_DIR="$(dirname "$(readlink -f "$0")")"
TEST_DIR="/tmp/plex-backup-test-$(date +%s)"
TEST_BACKUP_ROOT="$TEST_DIR/backups"
TEST_LOG_ROOT="$TEST_DIR/logs"
TEST_RESULTS_FILE="$TEST_DIR/test-results.json"
# Test counters
TESTS_RUN=0
TESTS_PASSED=0
TESTS_FAILED=0
declare -a FAILED_TESTS=()
# Logging functions
log_test() {
local timestamp=$(date '+%Y-%m-%d %H:%M:%S')
echo -e "${CYAN}[TEST ${timestamp}]${NC} $1"
}
log_pass() {
local timestamp=$(date '+%Y-%m-%d %H:%M:%S')
echo -e "${GREEN}[PASS ${timestamp}]${NC} $1"
TESTS_PASSED=$((TESTS_PASSED + 1))
}
log_fail() {
local timestamp=$(date '+%Y-%m-%d %H:%M:%S')
echo -e "${RED}[FAIL ${timestamp}]${NC} $1"
TESTS_FAILED=$((TESTS_FAILED + 1))
FAILED_TESTS+=("$1")
}
log_info() {
local timestamp=$(date '+%Y-%m-%d %H:%M:%S')
echo -e "${BLUE}[INFO ${timestamp}]${NC} $1"
}
log_warn() {
local timestamp=$(date '+%Y-%m-%d %H:%M:%S')
echo -e "${YELLOW}[WARN ${timestamp}]${NC} $1"
}
# Test framework functions
run_test() {
local test_name="$1"
local test_function="$2"
TESTS_RUN=$((TESTS_RUN + 1))
log_test "Running: $test_name"
if $test_function; then
log_pass "$test_name"
record_test_result "$test_name" "PASS" ""
else
log_fail "$test_name"
record_test_result "$test_name" "FAIL" "Test function returned non-zero exit code"
fi
}
record_test_result() {
local test_name="$1"
local status="$2"
local error_message="$3"
local timestamp=$(date -Iseconds)
# Initialize results file if it doesn't exist
if [ ! -f "$TEST_RESULTS_FILE" ]; then
echo "[]" > "$TEST_RESULTS_FILE"
fi
local result=$(jq -n \
--arg test_name "$test_name" \
--arg status "$status" \
--arg error_message "$error_message" \
--arg timestamp "$timestamp" \
'{
test_name: $test_name,
status: $status,
error_message: $error_message,
timestamp: $timestamp
}')
jq --argjson result "$result" '. += [$result]' "$TEST_RESULTS_FILE" > "${TEST_RESULTS_FILE}.tmp" && \
mv "${TEST_RESULTS_FILE}.tmp" "$TEST_RESULTS_FILE"
}
# Setup test environment
setup_test_environment() {
log_info "Setting up test environment in $TEST_DIR"
# Create test directories
mkdir -p "$TEST_DIR"
mkdir -p "$TEST_BACKUP_ROOT"
mkdir -p "$TEST_LOG_ROOT"
mkdir -p "$TEST_DIR/mock_plex"
# Create mock Plex files for testing
echo "PRAGMA user_version=1;" > "$TEST_DIR/mock_plex/com.plexapp.plugins.library.db"
echo "PRAGMA user_version=1;" > "$TEST_DIR/mock_plex/com.plexapp.plugins.library.blobs.db"
dd if=/dev/zero of="$TEST_DIR/mock_plex/Preferences.xml" bs=1024 count=1 2>/dev/null
# Create mock performance log
echo "[]" > "$TEST_DIR/mock-performance.json"
echo "{}" > "$TEST_DIR/mock-backup.json"
log_info "Test environment setup complete"
}
# Cleanup test environment
cleanup_test_environment() {
if [ -d "$TEST_DIR" ]; then
log_info "Cleaning up test environment"
rm -rf "$TEST_DIR"
fi
}
# Mock functions to replace actual backup script functions
mock_manage_plex_service() {
local action="$1"
echo "Mock: Plex service $action"
return 0
}
mock_calculate_checksum() {
local file="$1"
echo "$(echo "$file" | md5sum | cut -d' ' -f1)"
return 0
}
mock_verify_backup() {
local src="$1"
local dest="$2"
# Always return success for testing
return 0
}
# Test: JSON log initialization
test_json_log_initialization() {
local test_log="$TEST_DIR/test-init.json"
# Remove file if it exists
rm -f "$test_log"
# Test initialization
if [ ! -f "$test_log" ] || ! jq empty "$test_log" 2>/dev/null; then
echo "{}" > "$test_log"
fi
# Verify file exists and is valid JSON
if [ -f "$test_log" ] && jq empty "$test_log" 2>/dev/null; then
return 0
else
return 1
fi
}
# Test: Performance tracking
test_performance_tracking() {
local test_perf_log="$TEST_DIR/test-performance.json"
echo "[]" > "$test_perf_log"
# Mock performance tracking function
track_performance_test() {
local operation="$1"
local start_time="$2"
local end_time=$(date +%s)
local duration=$((end_time - start_time))
local entry=$(jq -n \
--arg operation "$operation" \
--arg duration "$duration" \
--arg timestamp "$(date -Iseconds)" \
'{
operation: $operation,
duration_seconds: ($duration | tonumber),
timestamp: $timestamp
}')
jq --argjson entry "$entry" '. += [$entry]' "$test_perf_log" > "${test_perf_log}.tmp" && \
mv "${test_perf_log}.tmp" "$test_perf_log"
}
# Test tracking
local start_time=$(date +%s)
sleep 1 # Simulate work
track_performance_test "test_operation" "$start_time"
# Verify entry was added
local entry_count=$(jq length "$test_perf_log")
if [ "$entry_count" -eq 1 ]; then
return 0
else
return 1
fi
}
# Test: Notification system
test_notification_system() {
# Mock notification function
send_notification_test() {
local title="$1"
local message="$2"
local status="${3:-info}"
# Just verify parameters are received correctly
if [ -n "$title" ] && [ -n "$message" ]; then
echo "Notification: $title - $message ($status)" > "$TEST_DIR/notification.log"
return 0
else
return 1
fi
}
# Test notification
send_notification_test "Test Title" "Test Message" "success"
# Verify notification was processed
if [ -f "$TEST_DIR/notification.log" ] && grep -q "Test Title" "$TEST_DIR/notification.log"; then
return 0
else
return 1
fi
}
# Test: Checksum caching
test_checksum_caching() {
local test_file="$TEST_DIR/checksum_test.txt"
local cache_file="${test_file}.md5"
# Create test file
echo "test content" > "$test_file"
# Mock checksum function with caching
calculate_checksum_test() {
local file="$1"
local cache_file="${file}.md5"
local file_mtime=$(stat -c %Y "$file" 2>/dev/null || echo "0")
# Check cache
if [ -f "$cache_file" ]; then
local cache_mtime=$(stat -c %Y "$cache_file" 2>/dev/null || echo "0")
if [ "$cache_mtime" -gt "$file_mtime" ]; then
cat "$cache_file"
return 0
fi
fi
# Calculate and cache
local checksum=$(md5sum "$file" | cut -d' ' -f1)
echo "$checksum" > "$cache_file"
echo "$checksum"
}
# First calculation (should create cache)
local checksum1=$(calculate_checksum_test "$test_file")
# Second calculation (should use cache)
local checksum2=$(calculate_checksum_test "$test_file")
# Verify checksums match and cache file exists
if [ "$checksum1" = "$checksum2" ] && [ -f "$cache_file" ]; then
return 0
else
return 1
fi
}
# Test: Backup verification
test_backup_verification() {
local src_file="$TEST_DIR/source.txt"
local dest_file="$TEST_DIR/backup.txt"
# Create identical files
echo "backup test content" > "$src_file"
cp "$src_file" "$dest_file"
# Mock verification function
verify_backup_test() {
local src="$1"
local dest="$2"
local src_checksum=$(md5sum "$src" | cut -d' ' -f1)
local dest_checksum=$(md5sum "$dest" | cut -d' ' -f1)
if [ "$src_checksum" = "$dest_checksum" ]; then
return 0
else
return 1
fi
}
# Test verification
if verify_backup_test "$src_file" "$dest_file"; then
return 0
else
return 1
fi
}
# Test: Parallel processing framework
test_parallel_processing() {
local temp_dir=$(mktemp -d)
local -a pids=()
local total_jobs=5
local completed_jobs=0
# Simulate parallel jobs
for i in $(seq 1 $total_jobs); do
(
# Simulate work
sleep 0.$i
echo "$i" > "$temp_dir/job_$i.result"
) &
pids+=($!)
done
# Wait for all jobs
for pid in "${pids[@]}"; do
if wait "$pid"; then
completed_jobs=$((completed_jobs + 1))
fi
done
# Verify all jobs completed
local result_files=$(find "$temp_dir" -name "job_*.result" | wc -l)
# Cleanup
rm -rf "$temp_dir"
if [ "$completed_jobs" -eq "$total_jobs" ] && [ "$result_files" -eq "$total_jobs" ]; then
return 0
else
return 1
fi
}
# Test: Database integrity check simulation
test_database_integrity() {
local test_db="$TEST_DIR/test.db"
# Create a simple SQLite database
sqlite3 "$test_db" "CREATE TABLE test (id INTEGER, name TEXT);"
sqlite3 "$test_db" "INSERT INTO test VALUES (1, 'test');"
# Mock integrity check
check_integrity_test() {
local db_file="$1"
# Use sqlite3 instead of Plex SQLite for testing
local result=$(sqlite3 "$db_file" "PRAGMA integrity_check;" 2>/dev/null)
if echo "$result" | grep -q "ok"; then
return 0
else
return 1
fi
}
# Test integrity check
if check_integrity_test "$test_db"; then
return 0
else
return 1
fi
}
# Test: Configuration parsing
test_configuration_parsing() {
# Mock command line parsing
parse_args_test() {
local args=("$@")
local auto_repair=false
local parallel=true
local webhook=""
for arg in "${args[@]}"; do
case "$arg" in
--auto-repair) auto_repair=true ;;
--no-parallel) parallel=false ;;
--webhook=*) webhook="${arg#*=}" ;;
esac
done
# Return parsed values
echo "$auto_repair $parallel $webhook"
}
# Test parsing
local result=$(parse_args_test --auto-repair --webhook=http://example.com)
if echo "$result" | grep -q "true true http://example.com"; then
return 0
else
return 1
fi
}
# Test: Error handling
test_error_handling() {
# Mock function that can fail
test_function_with_error() {
local should_fail="$1"
if [ "$should_fail" = "true" ]; then
return 1
else
return 0
fi
}
# Test success case
if test_function_with_error "false"; then
# Test failure case
if ! test_function_with_error "true"; then
return 0 # Both cases worked as expected
fi
fi
return 1
}
# Run all unit tests
run_all_tests() {
log_info "Setting up test environment"
setup_test_environment
log_info "Starting unit tests"
# Core functionality tests
run_test "JSON Log Initialization" test_json_log_initialization
run_test "Performance Tracking" test_performance_tracking
run_test "Notification System" test_notification_system
run_test "Checksum Caching" test_checksum_caching
run_test "Backup Verification" test_backup_verification
run_test "Parallel Processing" test_parallel_processing
run_test "Database Integrity Check" test_database_integrity
run_test "Configuration Parsing" test_configuration_parsing
run_test "Error Handling" test_error_handling
log_info "Unit tests completed"
}
# Run integration tests (requires actual Plex environment)
run_integration_tests() {
log_info "Starting integration tests"
log_warn "Integration tests require a working Plex installation"
# Check if Plex service exists
if ! systemctl list-units --all | grep -q plexmediaserver; then
log_warn "Plex service not found - skipping integration tests"
return 0
fi
# Test actual service management (if safe to do so)
log_info "Integration tests would test actual Plex service management"
log_info "Skipping for safety - implement with caution"
}
# Run performance tests
run_performance_tests() {
log_info "Starting performance benchmarks"
local start_time=$(date +%s)
# Test file operations
local test_file="$TEST_DIR/perf_test.dat"
dd if=/dev/zero of="$test_file" bs=1M count=10 2>/dev/null
# Benchmark checksum calculation
local checksum_start=$(date +%s)
md5sum "$test_file" > /dev/null
local checksum_time=$(($(date +%s) - checksum_start))
# Benchmark compression
local compress_start=$(date +%s)
tar -czf "$TEST_DIR/perf_test.tar.gz" -C "$TEST_DIR" "perf_test.dat"
local compress_time=$(($(date +%s) - compress_start))
local total_time=$(($(date +%s) - start_time))
log_info "Performance Results:"
log_info " Checksum (10MB): ${checksum_time}s"
log_info " Compression (10MB): ${compress_time}s"
log_info " Total benchmark time: ${total_time}s"
# Record performance data
local perf_entry=$(jq -n \
--arg checksum_time "$checksum_time" \
--arg compress_time "$compress_time" \
--arg total_time "$total_time" \
--arg timestamp "$(date -Iseconds)" \
'{
benchmark: "performance_test",
checksum_time_seconds: ($checksum_time | tonumber),
compress_time_seconds: ($compress_time | tonumber),
total_time_seconds: ($total_time | tonumber),
timestamp: $timestamp
}')
echo "$perf_entry" > "$TEST_DIR/performance_results.json"
}
# Generate comprehensive test report
generate_test_report() {
local timestamp=$(date '+%Y-%m-%d %H:%M:%S')
echo
echo "=============================================="
echo " PLEX BACKUP TEST REPORT"
echo "=============================================="
echo "Test Run: $timestamp"
echo "Tests Run: $TESTS_RUN"
echo "Tests Passed: $TESTS_PASSED"
echo "Tests Failed: $TESTS_FAILED"
echo
if [ $TESTS_FAILED -gt 0 ]; then
echo "FAILED TESTS:"
for failed_test in "${FAILED_TESTS[@]}"; do
echo " - $failed_test"
done
echo
fi
local success_rate=0
if [ $TESTS_RUN -gt 0 ]; then
success_rate=$(( (TESTS_PASSED * 100) / TESTS_RUN ))
fi
echo "Success Rate: ${success_rate}%"
echo
if [ $TESTS_FAILED -eq 0 ]; then
log_pass "All tests passed successfully!"
else
log_fail "Some tests failed - review output above"
fi
# Save detailed results
if [ -f "$TEST_RESULTS_FILE" ]; then
local report_file="$TEST_DIR/test_report_$(date +%Y%m%d_%H%M%S).json"
jq -n \
--arg timestamp "$timestamp" \
--arg tests_run "$TESTS_RUN" \
--arg tests_passed "$TESTS_PASSED" \
--arg tests_failed "$TESTS_FAILED" \
--arg success_rate "$success_rate" \
--argjson failed_tests "$(printf '%s\n' "${FAILED_TESTS[@]}" | jq -R . | jq -s .)" \
--argjson test_details "$(cat "$TEST_RESULTS_FILE")" \
'{
test_run_timestamp: $timestamp,
summary: {
tests_run: ($tests_run | tonumber),
tests_passed: ($tests_passed | tonumber),
tests_failed: ($tests_failed | tonumber),
success_rate_percent: ($success_rate | tonumber)
},
failed_tests: $failed_tests,
detailed_results: $test_details
}' > "$report_file"
log_info "Detailed test report saved to: $report_file"
fi
}
# Integration tests (if requested)
run_integration_tests() {
log_info "Running integration tests..."
# Note: These would require actual Plex installation
# For now, we'll just indicate what would be tested
log_warn "Integration tests require running Plex Media Server"
log_warn "These tests would cover:"
log_warn " - Service stop/start functionality"
log_warn " - Database integrity checks"
log_warn " - Full backup and restore cycles"
log_warn " - Performance under load"
}
# Performance benchmarks
run_performance_tests() {
log_info "Running performance benchmarks..."
local start_time=$(date +%s)
# Create large test files
local large_file="$TEST_DIR/large_test.db"
dd if=/dev/zero of="$large_file" bs=1M count=100 2>/dev/null
# Benchmark checksum calculation
local checksum_start=$(date +%s)
md5sum "$large_file" > /dev/null
local checksum_end=$(date +%s)
local checksum_time=$((checksum_end - checksum_start))
# Benchmark compression
local compress_start=$(date +%s)
tar -czf "$TEST_DIR/large_test.tar.gz" -C "$TEST_DIR" "large_test.db"
local compress_end=$(date +%s)
local compress_time=$((compress_end - compress_start))
local total_time=$(($(date +%s) - start_time))
log_info "Performance Results:"
log_info " Checksum (100MB): ${checksum_time}s"
log_info " Compression (100MB): ${compress_time}s"
log_info " Total benchmark time: ${total_time}s"
}
# Main execution
main() {
case "${1:-all}" in
"unit")
run_all_tests
;;
"integration")
run_integration_tests
;;
"performance")
run_performance_tests
;;
"all")
run_all_tests
# Uncomment for integration tests if environment supports it
# run_integration_tests
run_performance_tests
;;
*)
echo "Usage: $0 [unit|integration|performance|all]"
echo " unit - Run unit tests only"
echo " integration - Run integration tests (requires Plex)"
echo " performance - Run performance benchmarks"
echo " all - Run all available tests"
exit 1
;;
esac
generate_test_report
# Exit with appropriate code
if [ $TESTS_FAILED -gt 0 ]; then
exit 1
else
exit 0
fi
}
# Trap to ensure cleanup on exit
trap cleanup_test_environment EXIT
main "$@"