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-validator.bats - Unit tests for validator.sh
# Tests: validate_cli_binary(), validate_output_dir(), security checks
#

# Setup test environment
setup() {
    # Load validator module
    export SCRIPT_DIR="${BATS_TEST_DIRNAME}/../../core"
    source "${SCRIPT_DIR}/validator.sh"

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

    # Create temp directory for tests
    export TEST_TEMP_DIR="${BATS_TEST_TMPDIR}/validator-test-$$"
    mkdir -p "$TEST_TEMP_DIR"
}

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

# ===== validate_cli_binary() tests =====

@test "validate_cli_binary: accepts valid system binary" {
    run validate_cli_binary "/bin/echo"
    [ "$status" -eq 0 ]
    [[ "$output" == "/bin/echo" ]]
}

@test "validate_cli_binary: rejects empty binary name" {
    run validate_cli_binary ""
    [ "$status" -eq 1 ]
}

@test "validate_cli_binary: rejects non-existent binary" {
    run validate_cli_binary "/nonexistent/binary/path"
    [ "$status" -eq 1 ]
}

@test "validate_cli_binary: rejects path traversal attack (..)  " {
    run validate_cli_binary "/bin/../etc/passwd"
    [ "$status" -eq 1 ]
}

@test "validate_cli_binary: rejects dangerous characters (;)" {
    run validate_cli_binary "/bin/ls;rm -rf /"
    [ "$status" -eq 1 ]
}

@test "validate_cli_binary: rejects dangerous characters (\$)" {
    run validate_cli_binary "/bin/\$(whoami)"
    [ "$status" -eq 1 ]
}

@test "validate_cli_binary: rejects dangerous characters (|)" {
    run validate_cli_binary "/bin/ls|cat"
    [ "$status" -eq 1 ]
}

@test "validate_cli_binary: resolves binary from PATH" {
    run validate_cli_binary "echo"
    [ "$status" -eq 0 ]
    [[ "$output" =~ /echo$ ]]
}

@test "validate_cli_binary: validates executable permission" {
    # Create non-executable file
    local test_binary="$TEST_TEMP_DIR/non-executable"
    touch "$test_binary"
    chmod 644 "$test_binary"

    run validate_cli_binary "$test_binary"
    [ "$status" -eq 1 ]
}

@test "validate_cli_binary: accepts executable file" {
    # Create executable file
    local test_binary="$TEST_TEMP_DIR/executable"
    echo "#!/bin/bash" > "$test_binary"
    chmod 755 "$test_binary"

    run validate_cli_binary "$test_binary"
    [ "$status" -eq 0 ]
}

@test "validate_cli_binary: handles symbolic links" {
    local test_binary="$TEST_TEMP_DIR/test-bin"
    local test_link="$TEST_TEMP_DIR/test-link"

    # Create executable and symlink
    echo "#!/bin/bash" > "$test_binary"
    chmod 755 "$test_binary"
    ln -s "$test_binary" "$test_link"

    run validate_cli_binary "$test_link"
    [ "$status" -eq 0 ]
}

@test "validate_cli_binary: rejects system binaries without permission" {
    # Ensure permission is not granted
    export CLI_TEST_ALLOW_SYSTEM_BINARIES=false

    run validate_cli_binary "/usr/bin/python3"
    [ "$status" -eq 1 ]
}

@test "validate_cli_binary: allows system binaries with permission" {
    export CLI_TEST_ALLOW_SYSTEM_BINARIES=true

    run validate_cli_binary "/usr/bin/python3"
    [ "$status" -eq 0 ] || [ "$status" -eq 1 ]  # May fail if python3 not installed
}

@test "validate_cli_binary: allows system shells explicitly" {
    run validate_cli_binary "/bin/bash"
    [ "$status" -eq 0 ]
}

# ===== validate_output_dir() tests =====

@test "validate_output_dir: rejects empty directory" {
    run validate_output_dir ""
    [ "$status" -eq 1 ]
}

@test "validate_output_dir: creates non-existent directory" {
    local test_dir="$TEST_TEMP_DIR/new-output-dir"

    run validate_output_dir "$test_dir"
    [ "$status" -eq 0 ]
    [ -d "$test_dir" ]
}

@test "validate_output_dir: accepts existing writable directory" {
    run validate_output_dir "$TEST_TEMP_DIR"
    [ "$status" -eq 0 ]
}

@test "validate_output_dir: rejects /etc directory" {
    run validate_output_dir "/etc/test-output"
    [ "$status" -eq 1 ]
}

@test "validate_output_dir: rejects /usr directory" {
    run validate_output_dir "/usr/test-output"
    [ "$status" -eq 1 ]
}

@test "validate_output_dir: rejects /bin directory" {
    run validate_output_dir "/bin/test-output"
    [ "$status" -eq 1 ]
}

@test "validate_output_dir: rejects /sbin directory" {
    run validate_output_dir "/sbin/test-output"
    [ "$status" -eq 1 ]
}

@test "validate_output_dir: allows /tmp with warning" {
    run validate_output_dir "/tmp/test-output-$$"
    [ "$status" -eq 0 ]
    rm -rf "/tmp/test-output-$$" 2>/dev/null || true
}

@test "validate_output_dir: sets secure permissions (700)" {
    local test_dir="$TEST_TEMP_DIR/secure-dir"

    run validate_output_dir "$test_dir"
    [ "$status" -eq 0 ]

    # Check permissions (700 = rwx------)
    local perms
    perms=$(stat -f "%Lp" "$test_dir" 2>/dev/null || stat -c "%a" "$test_dir" 2>/dev/null)
    [ "$perms" = "700" ]
}

@test "validate_output_dir: rejects directory outside HOME and PWD" {
    # This test may vary based on environment
    skip "Environment-dependent test"
}

# ===== validate_safe_path() tests =====

@test "validate_safe_path: allows user directory paths" {
    run validate_safe_path "$HOME/test"
    [ "$status" -eq 0 ]
}

@test "validate_safe_path: allows current directory paths" {
    run validate_safe_path "$PWD/test"
    [ "$status" -eq 0 ]
}

@test "validate_safe_path: rejects /etc paths" {
    run validate_safe_path "/etc/passwd"
    [ "$status" -eq 1 ]
}

# ===== sanitize_env_var() tests =====

@test "sanitize_env_var: removes dangerous characters" {
    run sanitize_env_var "TEST_VAR" "value;\$(whoami)"
    [ "$status" -eq 0 ]
    [[ "$output" != *";"* ]]
    [[ "$output" != *"\$"* ]]
}

@test "sanitize_env_var: removes newlines" {
    run sanitize_env_var "TEST_VAR" $'value\nwith\nnewlines'
    [ "$status" -eq 0 ]
    [[ "$output" != *$'\n'* ]]
}

@test "sanitize_env_var: removes backticks" {
    run sanitize_env_var "TEST_VAR" 'value`whoami`'
    [ "$status" -eq 0 ]
    [[ "$output" != *'`'* ]]
}

# ===== validate_cli_args() tests =====

@test "validate_cli_args: accepts normal arguments" {
    run validate_cli_args "arg1" "arg2" "arg3"
    [ "$status" -eq 0 ]
}

@test "validate_cli_args: rejects NULL bytes" {
    run validate_cli_args $'arg\x00null'
    [ "$status" -eq 1 ]
}

@test "validate_cli_args: rejects extremely long arguments" {
    local long_arg
    long_arg=$(printf 'a%.0s' {1..15000})

    run validate_cli_args "$long_arg"
    [ "$status" -eq 1 ]
}

@test "validate_cli_args: accepts arguments with spaces" {
    run validate_cli_args "arg with spaces" "another arg"
    [ "$status" -eq 0 ]
}

# ===== Security-focused integration tests =====

@test "SECURITY: command injection attempt via binary name" {
    run validate_cli_binary "/bin/ls && rm -rf /"
    [ "$status" -eq 1 ]
}

@test "SECURITY: command injection attempt via output dir" {
    run validate_output_dir "/tmp/test; rm -rf /"
    [ "$status" -eq 1 ]
}

@test "SECURITY: path traversal via multiple .. sequences" {
    run validate_cli_binary "/bin/../../../../../../etc/passwd"
    [ "$status" -eq 1 ]
}

@test "SECURITY: environment variable injection" {
    run sanitize_env_var "PATH" "/safe:$PATH:\$(malicious)"
    [ "$status" -eq 0 ]
    [[ "$output" != *"\$"* ]]
    [[ "$output" != *"("* ]]
}