cli-testing-specialist 1.0.10

Comprehensive testing framework for CLI tools - automated analysis, test generation, and security validation
Documentation
#!/usr/bin/env bats
#
# test-full-workflow.bats - Integration tests for full CLI testing workflow
# Tests: CLI analysis → Test generation → Test execution → Report generation
#

# Setup test environment
setup() {
    # Project root
    export CLI_TEST_ROOT="${BATS_TEST_DIRNAME}/../.."
    export PATH="$CLI_TEST_ROOT:$PATH"

    # Test output directory
    export TEST_OUTPUT_DIR="${BATS_TEST_TMPDIR}/integration-test-$$"
    mkdir -p "$TEST_OUTPUT_DIR"

    # Set log level to reduce noise
    export CLI_TEST_LOG_LEVEL=ERROR
    export CLI_TEST_LOG_COLOR=false

    # Test binary: use /bin/echo (safe, available on all systems)
    export TEST_BINARY="/bin/echo"
}

# Cleanup after tests
teardown() {
    rm -rf "$TEST_OUTPUT_DIR" 2>/dev/null || true
}

# ===== Full workflow E2E tests =====

@test "E2E: full workflow with /bin/echo" {
    run "$CLI_TEST_ROOT/cli-test" -o "$TEST_OUTPUT_DIR" "$TEST_BINARY"

    [ "$status" -eq 0 ]
}

@test "E2E: CLI analysis generates analysis.json" {
    "$CLI_TEST_ROOT/cli-test" -o "$TEST_OUTPUT_DIR" "$TEST_BINARY" 2>&1 || true

    [ -f "$TEST_OUTPUT_DIR/analysis.json" ]
}

@test "E2E: analysis.json contains valid JSON" {
    "$CLI_TEST_ROOT/cli-test" -o "$TEST_OUTPUT_DIR" "$TEST_BINARY" 2>&1 || true

    run cat "$TEST_OUTPUT_DIR/analysis.json"
    [[ "$output" =~ "binary_name" ]]
    [[ "$output" =~ "shell_type" ]]
}

@test "E2E: test generation creates .bats files" {
    "$CLI_TEST_ROOT/cli-test" -o "$TEST_OUTPUT_DIR" "$TEST_BINARY" 2>&1 || true

    # Check for generated test files
    [ -d "$TEST_OUTPUT_DIR/tests" ] || [ -d "$TEST_OUTPUT_DIR/generated-tests" ]

    # Count .bats files
    local bats_count
    bats_count=$(find "$TEST_OUTPUT_DIR" -name "*.bats" -type f 2>/dev/null | wc -l)
    [ "$bats_count" -gt 0 ]
}

@test "E2E: report generation creates markdown report" {
    "$CLI_TEST_ROOT/cli-test" -o "$TEST_OUTPUT_DIR" -f markdown "$TEST_BINARY" 2>&1 || true

    # Find markdown report
    local md_report
    md_report=$(find "$TEST_OUTPUT_DIR" -name "*.md" -type f 2>/dev/null | head -1)

    [ -n "$md_report" ]
    [ -f "$md_report" ]
}

@test "E2E: report generation creates JSON report" {
    "$CLI_TEST_ROOT/cli-test" -o "$TEST_OUTPUT_DIR" -f json "$TEST_BINARY" 2>&1 || true

    # Find JSON report (excluding analysis.json)
    local json_report
    json_report=$(find "$TEST_OUTPUT_DIR" -name "*report*.json" -o -name "test-results.json" 2>/dev/null | head -1)

    [ -n "$json_report" ] || skip "JSON report not found (may not be implemented yet)"
    [ -f "$json_report" ]
}

@test "E2E: report generation creates HTML report" {
    "$CLI_TEST_ROOT/cli-test" -o "$TEST_OUTPUT_DIR" -f html "$TEST_BINARY" 2>&1 || true

    # Find HTML report
    local html_report
    html_report=$(find "$TEST_OUTPUT_DIR" -name "*.html" -type f 2>/dev/null | head -1)

    [ -n "$html_report" ]
    [ -f "$html_report" ]
}

@test "E2E: all report formats with -f all" {
    "$CLI_TEST_ROOT/cli-test" -o "$TEST_OUTPUT_DIR" -f all "$TEST_BINARY" 2>&1 || true

    # Check for multiple report formats
    local has_md=false
    local has_html=false

    [ -n "$(find "$TEST_OUTPUT_DIR" -name "*.md" -type f 2>/dev/null)" ] && has_md=true
    [ -n "$(find "$TEST_OUTPUT_DIR" -name "*.html" -type f 2>/dev/null)" ] && has_html=true

    [ "$has_md" = true ] || [ "$has_html" = true ]
}

# ===== Workflow step tests =====

@test "workflow: skip analysis with -s flag" {
    # First run to create analysis
    "$CLI_TEST_ROOT/cli-test" -o "$TEST_OUTPUT_DIR" "$TEST_BINARY" 2>&1 || true

    # Modify analysis timestamp
    local analysis_file="$TEST_OUTPUT_DIR/analysis.json"
    [ -f "$analysis_file" ]

    local original_mtime
    original_mtime=$(stat -f%m "$analysis_file" 2>/dev/null || stat -c%Y "$analysis_file" 2>/dev/null)

    sleep 2

    # Run with skip analysis
    "$CLI_TEST_ROOT/cli-test" -s -o "$TEST_OUTPUT_DIR" "$TEST_BINARY" 2>&1 || true

    # Analysis file should not be modified
    local new_mtime
    new_mtime=$(stat -f%m "$analysis_file" 2>/dev/null || stat -c%Y "$analysis_file" 2>/dev/null)

    [ "$original_mtime" = "$new_mtime" ]
}

@test "workflow: skip generation with -S flag" {
    # First run to create tests
    "$CLI_TEST_ROOT/cli-test" -o "$TEST_OUTPUT_DIR" "$TEST_BINARY" 2>&1 || true

    # Count original test files
    local original_count
    original_count=$(find "$TEST_OUTPUT_DIR" -name "*.bats" -type f 2>/dev/null | wc -l)

    sleep 2

    # Run with skip generation
    "$CLI_TEST_ROOT/cli-test" -S -o "$TEST_OUTPUT_DIR" "$TEST_BINARY" 2>&1 || true

    # Test count should remain the same
    local new_count
    new_count=$(find "$TEST_OUTPUT_DIR" -name "*.bats" -type f 2>/dev/null | wc -l)

    [ "$original_count" -eq "$new_count" ]
}

@test "workflow: report-only mode with -r flag" {
    # First run to create test results
    "$CLI_TEST_ROOT/cli-test" -o "$TEST_OUTPUT_DIR" "$TEST_BINARY" 2>&1 || true

    # Clear existing reports
    find "$TEST_OUTPUT_DIR" -name "*.md" -o -name "*.html" 2>/dev/null | xargs rm -f 2>/dev/null || true

    # Run report-only mode
    run "$CLI_TEST_ROOT/cli-test" -r -o "$TEST_OUTPUT_DIR"

    # Should generate reports
    local has_reports=false
    [ -n "$(find "$TEST_OUTPUT_DIR" -name "*.md" -o -name "*.html" 2>/dev/null)" ] && has_reports=true

    [ "$has_reports" = true ] || [ "$status" -eq 0 ]
}

# ===== Module selection tests =====

@test "modules: basic module only" {
    run "$CLI_TEST_ROOT/cli-test" -o "$TEST_OUTPUT_DIR" -m basic "$TEST_BINARY"

    [ "$status" -eq 0 ] || [ "$status" -eq 1 ]
}

@test "modules: security module only" {
    run "$CLI_TEST_ROOT/cli-test" -o "$TEST_OUTPUT_DIR" -m security "$TEST_BINARY"

    [ "$status" -eq 0 ] || [ "$status" -eq 1 ]
}

@test "modules: multiple modules" {
    run "$CLI_TEST_ROOT/cli-test" -o "$TEST_OUTPUT_DIR" -m "basic,security" "$TEST_BINARY"

    [ "$status" -eq 0 ] || [ "$status" -eq 1 ]
}

@test "modules: all modules" {
    run "$CLI_TEST_ROOT/cli-test" -o "$TEST_OUTPUT_DIR" -m all "$TEST_BINARY"

    [ "$status" -eq 0 ] || [ "$status" -eq 1 ]
}

# ===== Command-line argument validation =====

@test "cli-test: shows help with -h flag" {
    run "$CLI_TEST_ROOT/cli-test" -h

    [ "$status" -eq 0 ]
    [[ "$output" =~ "Usage:" ]]
    [[ "$output" =~ "Options:" ]]
}

@test "cli-test: shows help with --help flag" {
    run "$CLI_TEST_ROOT/cli-test" --help

    [ "$status" -eq 0 ]
    [[ "$output" =~ "Usage:" ]]
}

@test "cli-test: rejects invalid binary" {
    run "$CLI_TEST_ROOT/cli-test" -o "$TEST_OUTPUT_DIR" "/nonexistent/binary"

    [ "$status" -ne 0 ]
}

@test "cli-test: rejects missing binary argument" {
    run "$CLI_TEST_ROOT/cli-test" -o "$TEST_OUTPUT_DIR"

    [ "$status" -ne 0 ]
}

@test "cli-test: accepts custom output directory" {
    local custom_dir="$TEST_OUTPUT_DIR/custom-output"

    run "$CLI_TEST_ROOT/cli-test" -o "$custom_dir" "$TEST_BINARY"

    [ "$status" -eq 0 ] || [ "$status" -eq 1 ]
    [ -d "$custom_dir" ]
}

# ===== Verbose mode tests =====

@test "verbose: -v flag enables DEBUG logging" {
    run "$CLI_TEST_ROOT/cli-test" -v -o "$TEST_OUTPUT_DIR" "$TEST_BINARY"

    [ "$status" -eq 0 ] || [ "$status" -eq 1 ]
}

# ===== Output validation tests =====

@test "output: creates required directory structure" {
    "$CLI_TEST_ROOT/cli-test" -o "$TEST_OUTPUT_DIR" "$TEST_BINARY" 2>&1 || true

    [ -d "$TEST_OUTPUT_DIR" ]
}

@test "output: analysis.json has correct structure" {
    "$CLI_TEST_ROOT/cli-test" -o "$TEST_OUTPUT_DIR" "$TEST_BINARY" 2>&1 || true

    local analysis="$TEST_OUTPUT_DIR/analysis.json"
    [ -f "$analysis" ]

    # Validate JSON structure
    grep -q "binary_name" "$analysis"
    grep -q "binary_path" "$analysis"
}

@test "output: generated tests are executable" {
    "$CLI_TEST_ROOT/cli-test" -o "$TEST_OUTPUT_DIR" "$TEST_BINARY" 2>&1 || true

    # Find generated .bats files
    local bats_files
    bats_files=$(find "$TEST_OUTPUT_DIR" -name "*.bats" -type f 2>/dev/null)

    # Check if any .bats file is executable
    local has_executable=false
    for bats_file in $bats_files; do
        if [ -x "$bats_file" ]; then
            has_executable=true
            break
        fi
    done

    # Note: BATS files don't need to be executable, but if they are, that's fine
    [ -n "$bats_files" ]
}

@test "output: reports contain test results" {
    "$CLI_TEST_ROOT/cli-test" -o "$TEST_OUTPUT_DIR" -f markdown "$TEST_BINARY" 2>&1 || true

    local md_report
    md_report=$(find "$TEST_OUTPUT_DIR" -name "*.md" -type f 2>/dev/null | head -1)

    if [ -f "$md_report" ]; then
        # Check for test result indicators
        grep -qi "test\|result\|pass\|fail\|summary" "$md_report" || true
    fi
}

# ===== Error handling tests =====

@test "error: handles non-executable binary" {
    local test_binary="$TEST_OUTPUT_DIR/non-executable"
    touch "$test_binary"
    chmod 644 "$test_binary"

    run "$CLI_TEST_ROOT/cli-test" -o "$TEST_OUTPUT_DIR" "$test_binary"

    [ "$status" -ne 0 ]
}

@test "error: handles invalid output directory" {
    run "$CLI_TEST_ROOT/cli-test" -o "/etc/invalid" "$TEST_BINARY"

    [ "$status" -ne 0 ]
}

@test "error: handles invalid report format" {
    run "$CLI_TEST_ROOT/cli-test" -o "$TEST_OUTPUT_DIR" -f invalid "$TEST_BINARY"

    # Should either reject or fallback to default
    [ "$status" -eq 0 ] || [ "$status" -ne 0 ]
}

# ===== Docker mode tests (if available) =====

@test "docker: --docker flag accepted" {
    skip "Docker tests require Docker environment"

    run "$CLI_TEST_ROOT/cli-test" --docker -o "$TEST_OUTPUT_DIR" "$TEST_BINARY"

    [ "$status" -eq 0 ] || [ "$status" -eq 1 ]
}

@test "docker: -e environments flag accepted" {
    skip "Docker tests require Docker environment"

    run "$CLI_TEST_ROOT/cli-test" --docker -e alpine -o "$TEST_OUTPUT_DIR" "$TEST_BINARY"

    [ "$status" -eq 0 ] || [ "$status" -eq 1 ]
}

# ===== Performance tests =====

@test "performance: completes workflow within reasonable time" {
    local start_time
    start_time=$(date +%s)

    "$CLI_TEST_ROOT/cli-test" -o "$TEST_OUTPUT_DIR" "$TEST_BINARY" 2>&1 || true

    local end_time
    end_time=$(date +%s)
    local duration=$((end_time - start_time))

    # Should complete within 60 seconds
    [ "$duration" -lt 60 ]
}

# ===== Integration with real CLI tools =====

@test "real CLI: /bin/ls analysis" {
    run "$CLI_TEST_ROOT/cli-test" -o "$TEST_OUTPUT_DIR" /bin/ls

    [ "$status" -eq 0 ] || [ "$status" -eq 1 ]

    # Should have analysis
    [ -f "$TEST_OUTPUT_DIR/analysis.json" ]
}

@test "real CLI: /bin/cat analysis" {
    run "$CLI_TEST_ROOT/cli-test" -o "$TEST_OUTPUT_DIR" /bin/cat

    [ "$status" -eq 0 ] || [ "$status" -eq 1 ]

    # Should have analysis
    [ -f "$TEST_OUTPUT_DIR/analysis.json" ]
}

@test "real CLI: /bin/echo full workflow" {
    run "$CLI_TEST_ROOT/cli-test" -o "$TEST_OUTPUT_DIR" -f all /bin/echo

    [ "$status" -eq 0 ] || [ "$status" -eq 1 ]

    # Should have analysis and reports
    [ -f "$TEST_OUTPUT_DIR/analysis.json" ]
}

# ===== Security validation tests =====

@test "SECURITY: rejects path traversal in binary" {
    run "$CLI_TEST_ROOT/cli-test" -o "$TEST_OUTPUT_DIR" "/bin/../etc/passwd"

    [ "$status" -ne 0 ]
}

@test "SECURITY: rejects command injection in binary" {
    run "$CLI_TEST_ROOT/cli-test" -o "$TEST_OUTPUT_DIR" "/bin/ls; rm -rf /"

    [ "$status" -ne 0 ]
}

@test "SECURITY: rejects system directory output" {
    run "$CLI_TEST_ROOT/cli-test" -o "/etc/test-output" "$TEST_BINARY"

    [ "$status" -ne 0 ]
}