MediaImporter / test_runner.sh
1 contributor
1083 lines | 39.143kb
#!/bin/bash

# Media Importer Test Runner
# Comprehensive testing framework based on Development.md specifications

set -e

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
TEST_DIR="$SCRIPT_DIR/test"
SOURCE_DIR="$TEST_DIR/source"
DEST_DIR="$TEST_DIR/destination"
SAMPLES_DIR="$SCRIPT_DIR/sample"  # Fixed: was 'samples' but should be 'sample'
MEDIA_IMPORTER="$SCRIPT_DIR/media-importer.sh"
TEST_REPORTS_DIR="$SCRIPT_DIR/test_reports"

# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color

# Function to print colored output
print_color() {
    local color="$1"
    local message="$2"
    echo -e "${color}${message}${NC}"
}

# Function to create test directories
create_test_dirs() {
    print_color "$BLUE" "Creating test directories..."
    mkdir -p "$SOURCE_DIR"
    mkdir -p "$DEST_DIR"
    mkdir -p "$TEST_REPORTS_DIR"
}

# Function to clean test directory
clean_test_dir() {
    print_color "$BLUE" "Cleaning test directory..."
    rm -rf "$TEST_DIR"/*
}

# Function to copy sample files
copy_sample_files() {
    local source_pattern="$1"
    local target_dir="$2"

    if [[ -d "$SAMPLES_DIR/$source_pattern" ]]; then
        # Copy all files recursively from source to target directory
        find "$SAMPLES_DIR/$source_pattern" -type f \( -iname "*.jpg" -o -iname "*.jpeg" -o -iname "*.png" -o -iname "*.tiff" -o -iname "*.tif" -o -iname "*.cr2" -o -iname "*.nef" -o -iname "*.arw" -o -iname "*.dng" -o -iname "*.raw" -o -iname "*.mp4" -o -iname "*.mov" -o -iname "*.avi" -o -iname "*.mts" -o -iname "*.m2ts" -o -iname "*.mkv" -o -iname "*.wmv" -o -iname "*.3gp" -o -iname "*.m4v" \) -exec cp {} "$target_dir/" \; 2>/dev/null || true
        print_color "$GREEN" "Copied $source_pattern files to $target_dir"
    else
        print_color "$YELLOW" "Warning: $SAMPLES_DIR/$source_pattern not found. Path: $SAMPLES_DIR/$source_pattern"
    fi
}

# Function to capture directory state
capture_state() {
    local dir_path="$1"
    local output_file="$2"
    local label="$3"

    print_color "$BLUE" "Capturing $label directory state..."
    if [[ -d "$dir_path" ]]; then
        find "$dir_path" -type f | sort > "$output_file"
    else
        echo "# Directory does not exist or is empty" > "$output_file"
    fi
}

# Function to run test command and capture output
run_test_command() {
    local command="$1"
    local log_file="$2"

    print_color "$YELLOW" "Executing: $command"
    echo "$command" > "$log_file"
    echo "" >> "$log_file"
    echo "=== SCRIPT OUTPUT ===" >> "$log_file"

    # Run command and capture both stdout and stderr
    if eval "$command" >> "$log_file" 2>&1; then
        echo "" >> "$log_file"
        echo "=== COMMAND COMPLETED SUCCESSFULLY ===" >> "$log_file"
        return 0
    else
        echo "" >> "$log_file"
        echo "=== COMMAND FAILED ===" >> "$log_file"
        return 1
    fi
}

# Function to generate test report
generate_test_report() {
    local test_name="$1"
    local scenario="$2"
    local objective="$3"
    local files_used="$4"
    local command="$5"
    local result_status="$6"

    # Determine success/failure prefix
    local status_prefix
    if [[ $result_status -eq 0 ]]; then
        status_prefix="s"
    else
        status_prefix="f"
    fi

    # Format date as YYYYMMDD
    local date_stamp=$(date '+%Y%m%d')

    # Create filename with new format: [s|f]_date_restul_numelei
    local report_file="$TEST_REPORTS_DIR/${status_prefix}_${date_stamp}_${test_name}.md"

    print_color "$BLUE" "Generating test report: $report_file"

    cat > "$report_file" << EOF
# Test Report: $test_name

## Test Information
- **Date**: $(date)
- **Scenario**: $scenario
- **Objective**: $objective
- **Files Used**: $files_used

## Pre-Test State
### Source Directory Structure
\`\`\`
$(cat "$TEST_DIR/source_before.txt" 2>/dev/null || echo "# No pre-test source state captured")
\`\`\`

### Destination Directory Structure
\`\`\`
$(cat "$TEST_DIR/dest_before.txt" 2>/dev/null || echo "# No pre-test destination state captured")
\`\`\`

## Test Execution
### Command Used
\`\`\`bash
$command
\`\`\`

### Script Output
\`\`\`
$(cat "$TEST_DIR/import_log.txt" 2>/dev/null || echo "# No script output captured")
\`\`\`

## Post-Test State
### Source Directory Structure
\`\`\`
$(cat "$TEST_DIR/source_after.txt" 2>/dev/null || echo "# No post-test source state captured")
\`\`\`

### Destination Directory Structure
\`\`\`
$(cat "$TEST_DIR/dest_after.txt" 2>/dev/null || echo "# No post-test destination state captured")
\`\`\`

## Analysis and Verification
### Expected Results
- Files should be processed according to the test scenario
- No data loss should occur
- Proper error handling for edge cases

### Actual Results
- Test execution completed with status: $result_status

### Issues Found
- [ ] No issues detected
- [ ] Issues found (see notes below)

### Protections Verified
- [ ] Destination exclusion working
- [ ] Move confirmation functional
- [ ] No data loss detected
- [ ] UTC conversion correct (for QuickTime files)
- [ ] Unimportable files handling (if applicable)

## Conclusion
### Test Result
- [ ] PASSED
- [ ] FAILED
- [ ] PARTIAL (with notes)

### Notes
Test completed as per Development.md specifications.

### Files Generated
- \`test/source_before.txt\` - Pre-test source structure
- \`test/dest_before.txt\` - Pre-test destination structure
- \`test/source_after.txt\` - Post-test source structure
- \`test/dest_after.txt\` - Post-test destination structure
- \`test/import_log.txt\` - Full script execution log
- \`test/test_report.md\` - This report
EOF

    print_color "$GREEN" "Test report generated: $report_file"

    # Clean up old reports, keeping only the last 10
    cleanup_old_reports
}

# Function to clean up old test reports, keeping only the last 10
cleanup_old_reports() {
    if [[ -d "$TEST_REPORTS_DIR" ]]; then
        # Count total reports
        local total_reports=$(find "$TEST_REPORTS_DIR" -name "*.md" | wc -l)

        if [[ $total_reports -gt 10 ]]; then
            print_color "$BLUE" "Cleaning up old test reports (keeping last 10)..."

            # Find and remove oldest reports, keeping the 10 most recent
            find "$TEST_REPORTS_DIR" -name "*.md" -type f -printf '%T@ %p\n' | \
                sort -n | \
                head -n -$((10)) | \
                cut -d' ' -f2- | \
                xargs -r rm

            local remaining=$(find "$TEST_REPORTS_DIR" -name "*.md" | wc -l)
            print_color "$GREEN" "Kept $remaining most recent test reports"
        fi
    fi
}

# Test Case 0: Basic Functionality Test
run_basic_functionality_test() {
    print_color "$GREEN" "=== Running Test 0: Basic Functionality Test ==="

    clean_test_dir
    create_test_dirs

    # Setup test files
    copy_sample_files "media/sortable" "$SOURCE_DIR"

    # Capture pre-test state
    capture_state "$SOURCE_DIR" "$TEST_DIR/source_before.txt" "source"
    capture_state "$DEST_DIR" "$TEST_DIR/dest_before.txt" "destination"

    # Run test
    local command="\"$MEDIA_IMPORTER\" -s \"$SOURCE_DIR\" -d \"$DEST_DIR\" -v"
    local result=0
    run_test_command "$command" "$TEST_DIR/import_log.txt" || result=$?

    # Capture post-test state
    capture_state "$SOURCE_DIR" "$TEST_DIR/source_after.txt" "source"
    capture_state "$DEST_DIR" "$TEST_DIR/dest_after.txt" "destination"

    # Generate report
    generate_test_report \
        "Basic_Functionality" \
        "Processing files with valid EXIF data" \
        "Verify correct sorting and organization of media files" \
        "Sample media files with EXIF data from samples/media/sortable/" \
        "$command" \
        "$result"
}

# Test Case 1: Unimportable Files Test
run_unimportable_files_test() {
    print_color "$GREEN" "=== Running Test 1: Unimportable Files Test ==="

    clean_test_dir
    create_test_dirs

    # Setup test files - mix of sortable and unimportable
    copy_sample_files "media/sortable" "$SOURCE_DIR"
    copy_sample_files "media/unimportable" "$SOURCE_DIR"

    # Capture pre-test state
    capture_state "$SOURCE_DIR" "$TEST_DIR/source_before.txt" "source"
    capture_state "$DEST_DIR" "$TEST_DIR/dest_before.txt" "destination"

    # Run test (without --collect-unimportable flag)
    local command="\"$MEDIA_IMPORTER\" -s \"$SOURCE_DIR\" -d \"$DEST_DIR\" -v"
    local result=0
    run_test_command "$command" "$TEST_DIR/import_log.txt" || result=$?

    # Capture post-test state
    capture_state "$SOURCE_DIR" "$TEST_DIR/source_after.txt" "source"
    capture_state "$DEST_DIR" "$TEST_DIR/dest_after.txt" "destination"

    # Generate report
    generate_test_report \
        "Unimportable_Files_Handling" \
        "Testing files without EXIF data in root and subfolders" \
        "Verify proper handling of unimportable files without collection flag" \
        "Mixed sortable and unimportable files from samples/media/" \
        "$command" \
        "$result"
}

# Test Case 2: Mixed Content Test
run_mixed_content_test() {
    print_color "$GREEN" "=== Running Test 2: Mixed Content Test ==="

    clean_test_dir
    create_test_dirs

    # Setup separate folders for sortable vs unimportable
    mkdir -p "$SOURCE_DIR/sortable" "$SOURCE_DIR/unimportable"
    copy_sample_files "media/sortable" "$SOURCE_DIR/sortable"
    copy_sample_files "media/unimportable" "$SOURCE_DIR/unimportable"

    # Capture pre-test state
    capture_state "$SOURCE_DIR" "$TEST_DIR/source_before.txt" "source"
    capture_state "$DEST_DIR" "$TEST_DIR/dest_before.txt" "destination"

    # Run test
    local command="\"$MEDIA_IMPORTER\" -s \"$SOURCE_DIR\" -d \"$DEST_DIR\" -v"
    local result=0
    run_test_command "$command" "$TEST_DIR/import_log.txt" || result=$?

    # Capture post-test state
    capture_state "$SOURCE_DIR" "$TEST_DIR/source_after.txt" "source"
    capture_state "$DEST_DIR" "$TEST_DIR/dest_after.txt" "destination"

    # Generate report
    generate_test_report \
        "Mixed_Content" \
        "Processing sortable and unimportable files in separate folders" \
        "Verify cleanup behavior for folders with different file types" \
        "Separate folders: sortable/ and unimportable/ with respective file types" \
        "$command" \
        "$result"
}

# Test Case 3: Safety Protections Test
run_safety_protections_test() {
    print_color "$GREEN" "=== Running Test 3: Safety Protections Test ==="

    clean_test_dir
    create_test_dirs

    # Setup test files
    copy_sample_files "media/sortable" "$SOURCE_DIR"

    # Create a file in destination to test exclusion
    echo "test file" > "$DEST_DIR/test.txt"

    # Capture pre-test state
    capture_state "$SOURCE_DIR" "$TEST_DIR/source_before.txt" "source"
    capture_state "$DEST_DIR" "$TEST_DIR/dest_before.txt" "destination"

    # Run test
    local command="\"$MEDIA_IMPORTER\" -s \"$SOURCE_DIR\" -d \"$DEST_DIR\" -v"
    local result=0
    run_test_command "$command" "$TEST_DIR/import_log.txt" || result=$?

    # Capture post-test state
    capture_state "$SOURCE_DIR" "$TEST_DIR/source_after.txt" "source"
    capture_state "$DEST_DIR" "$TEST_DIR/dest_after.txt" "destination"

    # Generate report
    generate_test_report \
        "Safety_Protections" \
        "Testing destination exclusion and move confirmation" \
        "Verify data integrity protections prevent data loss" \
        "Sample files with pre-existing destination content" \
        "$command" \
        "$result"
}

# Test Case 4: UTC Conversion Test
run_utc_conversion_test() {
    print_color "$GREEN" "=== Running Test 4: UTC Conversion Test ==="

    clean_test_dir
    create_test_dirs

    # Setup test files (assuming QuickTime files are available)
    copy_sample_files "media/sortable" "$SOURCE_DIR"

    # Capture pre-test state
    capture_state "$SOURCE_DIR" "$TEST_DIR/source_before.txt" "source"
    capture_state "$DEST_DIR" "$TEST_DIR/dest_before.txt" "destination"

    # Run test
    local command="\"$MEDIA_IMPORTER\" -s \"$SOURCE_DIR\" -d \"$DEST_DIR\" -v"
    local result=0
    run_test_command "$command" "$TEST_DIR/import_log.txt" || result=$?

    # Capture post-test state
    capture_state "$SOURCE_DIR" "$TEST_DIR/source_after.txt" "source"
    capture_state "$DEST_DIR" "$TEST_DIR/dest_after.txt" "destination"

    # Generate report
    generate_test_report \
        "UTC_Conversion" \
        "Testing UTC timestamp conversion for QuickTime files" \
        "Verify correct UTC to local time conversion" \
        "QuickTime/Apple media files with UTC timestamps" \
        "$command" \
        "$result"
}

# Test Case 5: Subdirectory Processing Test
run_subdirectory_processing_test() {
    print_color "$GREEN" "=== Running Test 5: Subdirectory Processing Test ==="

    clean_test_dir
    create_test_dirs

    # Create nested directory structure
    mkdir -p "$SOURCE_DIR/level1/level2"
    copy_sample_files "media/sortable" "$SOURCE_DIR/level1/level2"

    # Capture pre-test state
    capture_state "$SOURCE_DIR" "$TEST_DIR/source_before.txt" "source"
    capture_state "$DEST_DIR" "$TEST_DIR/dest_before.txt" "destination"

    # Run test
    local command="\"$MEDIA_IMPORTER\" -s \"$SOURCE_DIR\" -d \"$DEST_DIR\" -v"
    local result=0
    run_test_command "$command" "$TEST_DIR/import_log.txt" || result=$?

    # Capture post-test state
    capture_state "$SOURCE_DIR" "$TEST_DIR/source_after.txt" "source"
    capture_state "$DEST_DIR" "$TEST_DIR/dest_after.txt" "destination"

    # Generate report
    generate_test_report \
        "Subdirectory_Processing" \
        "Testing processing of files in nested subdirectories" \
        "Verify recursive file discovery and processing" \
        "Files in nested directory structure (level1/level2/)" \
        "$command" \
        "$result"
}

# Test Case 6: Keep Empty Directories Test
run_keep_empty_dirs_test() {
    print_color "$GREEN" "=== Running Test 6: Keep Empty Directories Test ==="

    clean_test_dir
    create_test_dirs

    # Create directory structure with some empty dirs
    mkdir -p "$SOURCE_DIR/sortable" "$SOURCE_DIR/empty1" "$SOURCE_DIR/empty2"
    copy_sample_files "media/sortable" "$SOURCE_DIR/sortable"

    # Capture pre-test state
    capture_state "$SOURCE_DIR" "$TEST_DIR/source_before.txt" "source"
    capture_state "$DEST_DIR" "$TEST_DIR/dest_before.txt" "destination"

    # Run test with --keep-empty-dirs
    local command="\"$MEDIA_IMPORTER\" -s \"$SOURCE_DIR\" -d \"$DEST_DIR\" --keep-empty-dirs -v"
    local result=0
    run_test_command "$command" "$TEST_DIR/import_log.txt" || result=$?

    # Capture post-test state
    capture_state "$SOURCE_DIR" "$TEST_DIR/source_after.txt" "source"
    capture_state "$DEST_DIR" "$TEST_DIR/dest_after.txt" "destination"

    # Generate report
    generate_test_report \
        "Keep_Empty_Directories" \
        "Testing --keep-empty-dirs functionality" \
        "Verify empty directory preservation when flag is used" \
        "Directory structure with sortable files and empty directories" \
        "$command" \
        "$result"
}

# Test Case 7: Source Only Test
run_source_only_test() {
    print_color "$GREEN" "=== Running Test 7: Source Only Test ==="

    clean_test_dir
    create_test_dirs

    # Setup test files
    copy_sample_files "media/sortable" "$SOURCE_DIR"

    # Capture pre-test state
    capture_state "$SOURCE_DIR" "$TEST_DIR/source_before.txt" "source"
    capture_state "$SOURCE_DIR/sorted" "$TEST_DIR/dest_before.txt" "auto-destination"

    # Run test with only source parameter
    local command="\"$MEDIA_IMPORTER\" -s \"$SOURCE_DIR\" -v"
    local result=0
    run_test_command "$command" "$TEST_DIR/import_log.txt" || result=$?

    # Capture post-test state
    capture_state "$SOURCE_DIR" "$TEST_DIR/source_after.txt" "source"
    capture_state "$SOURCE_DIR/sorted" "$TEST_DIR/dest_after.txt" "auto-destination"

    # Generate report
    generate_test_report \
        "Source_Only_Test" \
        "Testing processing with only source parameter" \
        "Verify automatic creation of sorted subdirectory" \
        "Sample media files, no explicit destination specified" \
        "$command" \
        "$result"
}

# Test Case 8: Destination Inside Source Test
run_destination_inside_source_test() {
    print_color "$GREEN" "=== Running Test 8: Destination Inside Source Test ==="

    clean_test_dir
    create_test_dirs

    # Setup test files
    copy_sample_files "media/sortable" "$SOURCE_DIR"

    # Capture pre-test state
    capture_state "$SOURCE_DIR" "$TEST_DIR/source_before.txt" "source"
    capture_state "$SOURCE_DIR/sorted" "$TEST_DIR/dest_before.txt" "destination-inside-source"

    # Run test with destination explicitly inside source
    local command="\"$MEDIA_IMPORTER\" -s \"$SOURCE_DIR\" -d \"$SOURCE_DIR/sorted\" -v"
    local result=0
    run_test_command "$command" "$TEST_DIR/import_log.txt" || result=$?

    # Guard 1: no files should remain outside sorted for sortable-only dataset
    local outside_count
    outside_count=$(find "$SOURCE_DIR" -type f ! -path "$SOURCE_DIR/sorted/*" | wc -l | tr -d ' ')
    if [[ "$outside_count" != "0" ]]; then
        echo "Destination exclusion check failed: found $outside_count file(s) outside sorted" >> "$TEST_DIR/import_log.txt"
        result=1
    fi

    # Guard 2: destination exclusion should prevent sorted/sorted recursion
    local recursive_sorted_count
    recursive_sorted_count=$(find "$SOURCE_DIR/sorted" -type d -path "*/sorted/sorted*" 2>/dev/null | wc -l | tr -d ' ')
    if [[ "$recursive_sorted_count" != "0" ]]; then
        echo "Destination recursion check failed: found nested sorted/sorted directories" >> "$TEST_DIR/import_log.txt"
        result=1
    fi

    # Capture post-test state
    capture_state "$SOURCE_DIR" "$TEST_DIR/source_after.txt" "source"
    capture_state "$SOURCE_DIR/sorted" "$TEST_DIR/dest_after.txt" "destination-inside-source"

    # Generate report
    generate_test_report \
        "Destination_Inside_Source" \
        "Testing explicit destination inside source directory" \
        "Verify destination exclusion and no sorted/sorted recursion" \
        "Sortable sample media with destination set to source/sorted" \
        "$command" \
        "$result"
}

# Test Case 9: Verify Mode Test
run_verify_mode_test() {
    print_color "$GREEN" "=== Running Test 9: Verify Mode Test ==="

    clean_test_dir
    create_test_dirs

    # Setup test files
    copy_sample_files "media/sortable" "$SOURCE_DIR"

    # Capture pre-test state
    capture_state "$SOURCE_DIR" "$TEST_DIR/source_before.txt" "source"
    capture_state "$SOURCE_DIR/sorted" "$TEST_DIR/dest_before.txt" "auto-destination"

    local log_file="$TEST_DIR/import_log.txt"
    local result=0
    : > "$log_file"

    local command_default="cd \"$SOURCE_DIR\" && \"$MEDIA_IMPORTER\" --dry-run"
    local command_strict="cd \"$SOURCE_DIR\" && \"$MEDIA_IMPORTER\" --dry-run --verify-mode strict"
    local command="$command_default && $command_strict"

    echo "$command_default" >> "$log_file"
    echo "" >> "$log_file"
    echo "=== DEFAULT VERIFY MODE OUTPUT ===" >> "$log_file"
    if eval "$command_default" >> "$log_file" 2>&1; then
        if ! grep -q "Verify mode:[[:space:]]*size" "$log_file"; then
            echo "Expected default verify mode 'size' not found in output" >> "$log_file"
            result=1
        fi
    else
        result=1
    fi

    echo "" >> "$log_file"
    echo "$command_strict" >> "$log_file"
    echo "" >> "$log_file"
    echo "=== STRICT VERIFY MODE OUTPUT ===" >> "$log_file"
    if eval "$command_strict" >> "$log_file" 2>&1; then
        if ! grep -q "Verify mode:[[:space:]]*strict" "$log_file"; then
            echo "Expected strict verify mode not found in output" >> "$log_file"
            result=1
        fi
    else
        result=1
    fi

    if [[ $result -eq 0 ]]; then
        echo "" >> "$log_file"
        echo "=== COMMAND COMPLETED SUCCESSFULLY ===" >> "$log_file"
    else
        echo "" >> "$log_file"
        echo "=== COMMAND FAILED ===" >> "$log_file"
    fi

    # Capture post-test state
    capture_state "$SOURCE_DIR" "$TEST_DIR/source_after.txt" "source"
    capture_state "$SOURCE_DIR/sorted" "$TEST_DIR/dest_after.txt" "auto-destination"

    # Generate report
    generate_test_report \
        "Verify_Mode" \
        "Testing verify-mode defaults and strict override" \
        "Verify default mode is size and strict mode is accepted" \
        "Sample sortable media in source-only dry-run mode" \
        "$command" \
        "$result"
}

# Test Case 10: Timestamp Collision No-Overwrite Test
run_timestamp_collision_no_overwrite_test() {
    print_color "$GREEN" "=== Running Test 10: Timestamp Collision No-Overwrite Test ==="

    clean_test_dir
    create_test_dirs

    local log_file="$TEST_DIR/import_log.txt"
    local result=0
    : > "$log_file"

    if ! command -v ffmpeg >/dev/null 2>&1; then
        echo "ffmpeg is required for this regression test" >> "$log_file"
        result=1
    elif ! command -v exiftool >/dev/null 2>&1; then
        echo "exiftool is required for this regression test" >> "$log_file"
        result=1
    else
        ffmpeg -hide_banner -loglevel error -f lavfi -i color=c=blue:s=160x120:d=1 -c:v libx264 -pix_fmt yuv420p "$SOURCE_DIR/GX011621.MP4" >> "$log_file" 2>&1 || result=1
        ffmpeg -hide_banner -loglevel error -f lavfi -i color=c=red:s=160x120:d=1 -c:v libx264 -pix_fmt yuv420p "$SOURCE_DIR/GX021621.MP4" >> "$log_file" 2>&1 || result=1
        ffmpeg -hide_banner -loglevel error -f lavfi -i color=c=green:s=160x120:d=1 -c:v libx264 -pix_fmt yuv420p "$SOURCE_DIR/GX031621.MP4" >> "$log_file" 2>&1 || result=1
        exiftool -overwrite_original -CreateDate="2026:05:15 19:20:09" "$SOURCE_DIR/GX011621.MP4" "$SOURCE_DIR/GX021621.MP4" "$SOURCE_DIR/GX031621.MP4" >> "$log_file" 2>&1 || result=1
        touch -t 202605151920.09 "$SOURCE_DIR/GX011621.MP4" "$SOURCE_DIR/GX021621.MP4" "$SOURCE_DIR/GX031621.MP4" || result=1
    fi

    # Capture pre-test state
    capture_state "$SOURCE_DIR" "$TEST_DIR/source_before.txt" "source"
    capture_state "$DEST_DIR" "$TEST_DIR/dest_before.txt" "destination"

    local command="\"$MEDIA_IMPORTER\" -s \"$SOURCE_DIR\" -d \"$DEST_DIR\" --verify-mode size -v"
    if [[ $result -eq 0 ]]; then
        run_test_command "$command" "$log_file" || result=$?
    fi

    local dest_count
    dest_count=$(find "$DEST_DIR" -type f -iname "*.mp4" | wc -l | tr -d ' ')
    if [[ "$dest_count" != "3" ]]; then
        echo "Expected 3 destination MP4 files after timestamp collision import, found $dest_count" >> "$log_file"
        result=1
    fi

    local source_count
    source_count=$(find "$SOURCE_DIR" -type f -iname "*.mp4" | wc -l | tr -d ' ')
    if [[ "$source_count" != "0" ]]; then
        echo "Expected source MP4 files to be moved, found $source_count remaining" >> "$log_file"
        result=1
    fi

    local unique_names
    unique_names=$(find "$DEST_DIR" -type f -iname "*.mp4" -exec basename {} \; | sort -u | wc -l | tr -d ' ')
    if [[ "$unique_names" != "3" ]]; then
        echo "Expected 3 unique destination filenames, found $unique_names" >> "$log_file"
        result=1
    fi

    local expected_base="$DEST_DIR/2026-05-15/2026-05-15_19-20-09.mp4"
    local expected_suffix_1="$DEST_DIR/2026-05-15/2026-05-15_19-20-09_1.mp4"
    local expected_suffix_2="$DEST_DIR/2026-05-15/2026-05-15_19-20-09_2.mp4"
    if [[ ! -f "$expected_base" || ! -f "$expected_suffix_1" || ! -f "$expected_suffix_2" ]]; then
        echo "Expected unattended numeric conflict suffixes _1 and _2 were not created" >> "$log_file"
        result=1
    fi

    local legacy_suffix_count
    legacy_suffix_count=$(find "$DEST_DIR" -type f -name "*__GX*.mp4" | wc -l | tr -d ' ')
    if [[ "$legacy_suffix_count" != "0" ]]; then
        echo "Expected no legacy original-name conflict suffixes, found $legacy_suffix_count" >> "$log_file"
        result=1
    fi

    # Capture post-test state
    capture_state "$SOURCE_DIR" "$TEST_DIR/source_after.txt" "source"
    capture_state "$DEST_DIR" "$TEST_DIR/dest_after.txt" "destination"

    # Generate report
    generate_test_report \
        "Timestamp_Collision_No_Overwrite" \
        "Processing multiple GoPro-style chapters with identical CreateDate values" \
        "Verify timestamp filename collisions do not overwrite prior imports or delete sources" \
        "Synthetic MP4 files with identical QuickTime CreateDate metadata" \
        "$command" \
        "$result"
}

# Test Case 11: GoPro Sidecar Metadata Sync Test
run_gopro_sidecar_metadata_sync_test() {
    print_color "$GREEN" "=== Running Test 11: GoPro Sidecar Metadata Sync Test ==="

    clean_test_dir
    create_test_dirs

    local log_file="$TEST_DIR/import_log.txt"
    local result=0
    : > "$log_file"

    if ! command -v ffmpeg >/dev/null 2>&1; then
        echo "ffmpeg is required for this regression test" >> "$log_file"
        result=1
    elif ! command -v exiftool >/dev/null 2>&1; then
        echo "exiftool is required for this regression test" >> "$log_file"
        result=1
    else
        ffmpeg -hide_banner -loglevel error -f lavfi -i color=c=blue:s=160x120:d=1 -c:v libx264 -pix_fmt yuv420p "$SOURCE_DIR/GX011622.MP4" >> "$log_file" 2>&1 || result=1
        ffmpeg -hide_banner -loglevel error -f lavfi -i color=c=red:s=160x120:d=1 -c:v libx264 -pix_fmt yuv420p "$SOURCE_DIR/GX021622.MP4" >> "$log_file" 2>&1 || result=1
        ffmpeg -hide_banner -loglevel error -f lavfi -i color=c=green:s=160x120:d=1 -c:v libx264 -pix_fmt yuv420p "$SOURCE_DIR/GX031622.MP4" >> "$log_file" 2>&1 || result=1
        ffmpeg -hide_banner -loglevel error -f lavfi -i color=c=yellow:s=160x120:d=1 -c:v libx264 -pix_fmt yuv420p "$SOURCE_DIR/GX041622.MP4" >> "$log_file" 2>&1 || result=1
        exiftool -overwrite_original -QuickTime:CreateDate="2026:05:16 19:03:19" "$SOURCE_DIR/GX011622.MP4" "$SOURCE_DIR/GX021622.MP4" "$SOURCE_DIR/GX031622.MP4" "$SOURCE_DIR/GX041622.MP4" >> "$log_file" 2>&1 || result=1
        touch -t 202605161903.20 "$SOURCE_DIR/GX011622.THM" || result=1
        touch -t 202605161915.08 "$SOURCE_DIR/GX021622.THM" || result=1
        touch -t 202605161926.56 "$SOURCE_DIR/GX031622.LRV" || result=1
        touch -t 202605161938.44 "$SOURCE_DIR/GX041622.MP4" || result=1
    fi

    capture_state "$SOURCE_DIR" "$TEST_DIR/source_before.txt" "source"
    capture_state "$DEST_DIR" "$TEST_DIR/dest_before.txt" "destination"

    local command="\"$MEDIA_IMPORTER\" -s \"$SOURCE_DIR\" -d \"$DEST_DIR\" --verify-mode size -v"
    if [[ $result -eq 0 ]]; then
        run_test_command "$command" "$log_file" || result=$?
    fi

    local first_dest="$DEST_DIR/2026-05-16/2026-05-16_19-03-20.mp4"
    local second_dest="$DEST_DIR/2026-05-16/2026-05-16_19-15-08.mp4"
    local third_dest="$DEST_DIR/2026-05-16/2026-05-16_19-26-56.mp4"
    local fourth_dest="$DEST_DIR/2026-05-16/2026-05-16_19-38-44.mp4"
    if [[ ! -f "$first_dest" || ! -f "$second_dest" || ! -f "$third_dest" || ! -f "$fourth_dest" ]]; then
        echo "Expected GoPro filesystem-based destination filenames were not created" >> "$log_file"
        result=1
    fi

    local first_metadata second_metadata third_metadata fourth_metadata
    if [[ -f "$first_dest" ]]; then
        first_metadata=$(exiftool -api QuickTimeUTC=0 -s -s -s -QuickTime:CreateDate "$first_dest" 2>/dev/null | head -1)
        if [[ "$first_metadata" != "2026:05:16 19:03:20" ]]; then
            echo "Expected first imported metadata CreateDate 2026:05:16 19:03:20, got ${first_metadata:-none}" >> "$log_file"
            result=1
        fi
    fi
    if [[ -f "$third_dest" ]]; then
        third_metadata=$(exiftool -api QuickTimeUTC=0 -s -s -s -QuickTime:CreateDate "$third_dest" 2>/dev/null | head -1)
        if [[ "$third_metadata" != "2026:05:16 19:26:56" ]]; then
            echo "Expected third imported metadata CreateDate 2026:05:16 19:26:56, got ${third_metadata:-none}" >> "$log_file"
            result=1
        fi
    fi
    if [[ -f "$fourth_dest" ]]; then
        fourth_metadata=$(exiftool -api QuickTimeUTC=0 -s -s -s -QuickTime:CreateDate "$fourth_dest" 2>/dev/null | head -1)
        if [[ "$fourth_metadata" != "2026:05:16 19:38:44" ]]; then
            echo "Expected fourth imported metadata CreateDate 2026:05:16 19:38:44, got ${fourth_metadata:-none}" >> "$log_file"
            result=1
        fi
    fi
    if [[ -f "$second_dest" ]]; then
        second_metadata=$(exiftool -api QuickTimeUTC=0 -s -s -s -QuickTime:CreateDate "$second_dest" 2>/dev/null | head -1)
        if [[ "$second_metadata" != "2026:05:16 19:15:08" ]]; then
            echo "Expected second imported metadata CreateDate 2026:05:16 19:15:08, got ${second_metadata:-none}" >> "$log_file"
            result=1
        fi
    fi

    local source_count
    source_count=$(find "$SOURCE_DIR" -type f -iname "*.mp4" | wc -l | tr -d ' ')
    if [[ "$source_count" != "0" ]]; then
        echo "Expected GoPro source MP4 files to be moved, found $source_count remaining" >> "$log_file"
        result=1
    fi

    capture_state "$SOURCE_DIR" "$TEST_DIR/source_after.txt" "source"
    capture_state "$DEST_DIR" "$TEST_DIR/dest_after.txt" "destination"

    generate_test_report \
        "GoPro_Sidecar_Metadata_Sync" \
        "Processing GoPro-style MP4 chapters with THM, LRV, and MP4 filesystem timestamps" \
        "Verify GoPro imports use filesystem fallback dates and sync destination metadata automatically" \
        "Synthetic GoPro MP4 files with shared QuickTime CreateDate plus THM/LRV/no-sidecar mtime variants" \
        "$command" \
        "$result"
}

# Test Case 12: GoPro No-Sidecar Reimport Test
run_gopro_no_sidecar_reimport_test() {
    print_color "$GREEN" "=== Running Test 12: GoPro No-Sidecar Reimport Test ==="

    clean_test_dir
    create_test_dirs

    local log_file="$TEST_DIR/import_log.txt"
    local result=0
    : > "$log_file"

    if ! command -v ffmpeg >/dev/null 2>&1; then
        echo "ffmpeg is required for this regression test" >> "$log_file"
        result=1
    elif ! command -v exiftool >/dev/null 2>&1; then
        echo "exiftool is required for this regression test" >> "$log_file"
        result=1
    else
        ffmpeg -hide_banner -loglevel error -f lavfi -i color=c=purple:s=160x120:d=1 -c:v libx264 -pix_fmt yuv420p "$SOURCE_DIR/GX051622.MP4" >> "$log_file" 2>&1 || result=1
        exiftool -overwrite_original -QuickTime:CreateDate="2026:05:16 19:03:19" "$SOURCE_DIR/GX051622.MP4" >> "$log_file" 2>&1 || result=1
        rm -f "$SOURCE_DIR/GX051622.THM" "$SOURCE_DIR/GX051622.thm" "$SOURCE_DIR/GX051622.LRV" "$SOURCE_DIR/GX051622.lrv"
        touch -t 202605161950.32 "$SOURCE_DIR/GX051622.MP4" || result=1
    fi

    capture_state "$SOURCE_DIR" "$TEST_DIR/source_before.txt" "source"
    capture_state "$DEST_DIR" "$TEST_DIR/dest_before.txt" "destination"

    local command="\"$MEDIA_IMPORTER\" -s \"$SOURCE_DIR\" -d \"$DEST_DIR\" --verify-mode size -k -v && \"$MEDIA_IMPORTER\" -s \"$SOURCE_DIR\" -d \"$DEST_DIR\" --verify-mode size -k -v"
    if [[ $result -eq 0 ]]; then
        run_test_command "$command" "$log_file" || result=$?
    fi

    local first_dest="$DEST_DIR/2026-05-16/2026-05-16_19-50-32.mp4"
    local reimport_dest="$DEST_DIR/2026-05-16/2026-05-16_19-50-32_1.mp4"
    if [[ ! -f "$first_dest" || ! -f "$reimport_dest" ]]; then
        echo "Expected no-sidecar GoPro reimport destinations were not created" >> "$log_file"
        result=1
    fi

    local dest_count
    dest_count=$(find "$DEST_DIR" -type f -iname "*.mp4" | wc -l | tr -d ' ')
    if [[ "$dest_count" != "2" ]]; then
        echo "Expected 2 destination MP4 files after reimport, found $dest_count" >> "$log_file"
        result=1
    fi

    local source_count
    source_count=$(find "$SOURCE_DIR" -type f -iname "*.mp4" | wc -l | tr -d ' ')
    if [[ "$source_count" != "1" ]]; then
        echo "Expected source MP4 to remain in keep-originals reimport test, found $source_count" >> "$log_file"
        result=1
    fi

    local sidecar_count
    sidecar_count=$(find "$SOURCE_DIR" -type f \( -iname "*.thm" -o -iname "*.lrv" \) | wc -l | tr -d ' ')
    if [[ "$sidecar_count" != "0" ]]; then
        echo "Expected no THM/LRV sidecars in no-sidecar test, found $sidecar_count" >> "$log_file"
        result=1
    fi

    if ! grep -q "Filesystem:GX051622.MP4" "$log_file"; then
        echo "Expected date source fallback to Filesystem:GX051622.MP4" >> "$log_file"
        result=1
    fi

    local first_metadata reimport_metadata
    if [[ -f "$first_dest" ]]; then
        first_metadata=$(exiftool -api QuickTimeUTC=0 -s -s -s -QuickTime:CreateDate "$first_dest" 2>/dev/null | head -1)
        if [[ "$first_metadata" != "2026:05:16 19:50:32" ]]; then
            echo "Expected first import metadata CreateDate 2026:05:16 19:50:32, got ${first_metadata:-none}" >> "$log_file"
            result=1
        fi
    fi
    if [[ -f "$reimport_dest" ]]; then
        reimport_metadata=$(exiftool -api QuickTimeUTC=0 -s -s -s -QuickTime:CreateDate "$reimport_dest" 2>/dev/null | head -1)
        if [[ "$reimport_metadata" != "2026:05:16 19:50:32" ]]; then
            echo "Expected reimport metadata CreateDate 2026:05:16 19:50:32, got ${reimport_metadata:-none}" >> "$log_file"
            result=1
        fi
    fi

    capture_state "$SOURCE_DIR" "$TEST_DIR/source_after.txt" "source"
    capture_state "$DEST_DIR" "$TEST_DIR/dest_after.txt" "destination"

    generate_test_report \
        "GoPro_No_Sidecar_Reimport" \
        "Importing and reimporting a GoPro MP4 with no THM/LRV sidecars" \
        "Verify MP4 filesystem mtime fallback, metadata sync, and numeric suffix on reimport" \
        "Synthetic GoPro MP4 with no sidecars and wrong QuickTime metadata" \
        "$command" \
        "$result"
}

# Function to show menu
show_menu() {
    echo ""
    print_color "$GREEN" "Media Importer Test Runner"
    echo "=========================="
    echo ""
    echo "Available Tests:"
    echo "0. Basic Functionality Test"
    echo "1. Unimportable Files Test"
    echo "2. Mixed Content Test"
    echo "3. Safety Protections Test"
    echo "4. UTC Conversion Test"
    echo "5. Subdirectory Processing Test"
    echo "6. Keep Empty Directories Test"
    echo "7. Source Only Test"
    echo "8. Destination Inside Source Test"
    echo "9. Verify Mode Test"
    echo "10. Timestamp Collision No-Overwrite Test"
    echo "11. GoPro Sidecar Metadata Sync Test"
    echo "12. GoPro No-Sidecar Reimport Test"
    echo "13. Run All Tests"
    echo "q. Quit"
    echo ""
    echo -n "Select test to run (0-13, q to quit): "
}

# Function to run all tests
run_all_tests() {
    print_color "$GREEN" "Running all tests sequentially..."

    run_basic_functionality_test
    echo ""
    run_unimportable_files_test
    echo ""
    run_mixed_content_test
    echo ""
    run_safety_protections_test
    echo ""
    run_utc_conversion_test
    echo ""
    run_subdirectory_processing_test
    echo ""
    run_keep_empty_dirs_test
    echo ""
    run_source_only_test
    echo ""
    run_destination_inside_source_test
    echo ""
    run_verify_mode_test
    echo ""
    run_timestamp_collision_no_overwrite_test
    echo ""
    run_gopro_sidecar_metadata_sync_test
    echo ""
    run_gopro_no_sidecar_reimport_test

    print_color "$GREEN" "All tests completed!"
}

# Main menu loop
main() {
    while true; do
        show_menu
        read -r choice

        case $choice in
            0)
                run_basic_functionality_test
                ;;
            1)
                run_unimportable_files_test
                ;;
            2)
                run_mixed_content_test
                ;;
            3)
                run_safety_protections_test
                ;;
            4)
                run_utc_conversion_test
                ;;
            5)
                run_subdirectory_processing_test
                ;;
            6)
                run_keep_empty_dirs_test
                ;;
            7)
                run_source_only_test
                ;;
            8)
                run_destination_inside_source_test
                ;;
            9)
                run_verify_mode_test
                ;;
            10)
                run_timestamp_collision_no_overwrite_test
                ;;
            11)
                run_gopro_sidecar_metadata_sync_test
                ;;
            12)
                run_gopro_no_sidecar_reimport_test
                ;;
            13)
                run_all_tests
                ;;
            q|Q)
                print_color "$GREEN" "Exiting test runner."
                exit 0
                ;;
            *)
                print_color "$RED" "Invalid choice. Please select 0-13 or q to quit."
                ;;
        esac

        echo ""
        echo -n "Press Enter to continue..."
        read -r
    done
}

# Check if script exists
if [[ ! -f "$MEDIA_IMPORTER" ]]; then
    print_color "$RED" "Error: $MEDIA_IMPORTER not found!"
    exit 1
fi

# Check if samples directory exists
if [[ ! -d "$SAMPLES_DIR" ]]; then
    print_color "$YELLOW" "Warning: $SAMPLES_DIR not found. Some tests may not work properly."
fi

# Run main menu if no arguments provided
if [[ $# -eq 0 ]]; then
    main
else
    # Handle command line arguments
    case "$1" in
        "basic"|"0")
            run_basic_functionality_test
            ;;
        "unimportable"|"1")
            run_unimportable_files_test
            ;;
        "mixed"|"2")
            run_mixed_content_test
            ;;
        "safety"|"3")
            run_safety_protections_test
            ;;
        "utc"|"4")
            run_utc_conversion_test
            ;;
        "subdir"|"5")
            run_subdirectory_processing_test
            ;;
        "keep-empty"|"6")
            run_keep_empty_dirs_test
            ;;
        "source-only"|"7")
            run_source_only_test
            ;;
        "dest-in-source"|"8")
            run_destination_inside_source_test
            ;;
        "verify-mode"|"9")
            run_verify_mode_test
            ;;
        "timestamp-collision"|"collision"|"10")
            run_timestamp_collision_no_overwrite_test
            ;;
        "gopro-sidecar-sync"|"gopro-sync"|"11")
            run_gopro_sidecar_metadata_sync_test
            ;;
        "gopro-no-sidecar-reimport"|"gopro-reimport"|"12")
            run_gopro_no_sidecar_reimport_test
            ;;
        "all"|"13")
            run_all_tests
            ;;
        "clean")
            clean_test_dir
            ;;
        "setup")
            create_test_dirs
            ;;
        *)
            print_color "$RED" "Unknown test: $1"
            echo "Usage: $0 [basic|unimportable|mixed|safety|utc|subdir|keep-empty|source-only|dest-in-source|verify-mode|timestamp-collision|gopro-sidecar-sync|gopro-no-sidecar-reimport|all|clean|setup] or [0-13]"
            exit 1
            ;;
    esac
fi