guardy 0.2.4

Fast, secure git hooks in Rust with secret scanning and protected file synchronization
Documentation
use std::{env, process::Command};

/// Test environment variable precedence for boolean configuration values
/// Ensures that environment variables override CLI defaults but not explicit CLI args
#[test]
fn test_env_var_precedence_boolean_fields() {
    // Try multiple possible locations for the binary
    let guardy_binary = env::var("CARGO_BIN_EXE_guardy")
        .or_else(|_| {
            // Try relative paths from test directory
            let paths = [
                "target/release/guardy",
                "target/debug/guardy",
                "../../target/release/guardy",
                "../../target/debug/guardy",
            ];
            for path in &paths {
                if std::path::Path::new(path).exists() {
                    return Ok(path.to_string());
                }
            }
            Err("Could not find guardy binary")
        })
        .expect("Failed to locate guardy binary for testing");

    // Create a test file with a fake secret
    std::fs::write(
        "/tmp/test_secrets.txt",
        "sk-1234567890abcdefghijklmnopqrstuvwxyz123456",
    )
    .unwrap();

    // Test 1: GUARDY_SCAN_SHOW=1 should override default false value
    let output = Command::new(&guardy_binary)
        .args(["scan", "/tmp/test_secrets.txt"])
        .env("GUARDY_SCAN_SHOW", "1")
        .env("RUST_LOG", "debug")
        .output()
        .expect("Failed to execute guardy");

    let stderr = String::from_utf8_lossy(&output.stderr);
    let stdout = String::from_utf8_lossy(&output.stdout);

    println!("Binary path: {guardy_binary}");
    println!("Exit status: {}", output.status);
    println!("Stdout: {stdout}");
    println!("Stderr: {stderr}");

    // Check if command executed successfully
    if !output.status.success() {
        panic!(
            "Command failed with status: {}. Stdout: {stdout}. Stderr: {stderr}",
            output.status
        );
    }

    // Debug logs go to stdout, not stderr
    let logs = format!("{stdout}{stderr}");

    // Verify environment variable was parsed
    assert!(
        logs.contains("Found env var GUARDY_SCAN_SHOW (type: bool): 1"),
        "Environment variable should be parsed. Output: {logs}"
    );

    // Verify it was parsed as true
    assert!(
        logs.contains("Parsed GUARDY_SCAN_SHOW as boolean: true"),
        "Environment variable should be parsed as true. Output: {logs}"
    );

    // Most importantly: Verify it affected the final configuration
    assert!(
        logs.contains("scanner config check") && logs.contains("show: true"),
        "Environment variable should override default in final scanner config. Output: {logs}"
    );

    // Test 2: GUARDY_SCAN_SENSITIVE=1 should override default false value
    let output = Command::new(&guardy_binary)
        .args(["scan", "/dev/null"])
        .env("GUARDY_SCAN_SENSITIVE", "1")
        .env("RUST_LOG", "debug")
        .output()
        .expect("Failed to execute guardy");

    let stderr = String::from_utf8_lossy(&output.stderr);
    let stdout = String::from_utf8_lossy(&output.stdout);
    let logs = format!("{stdout}{stderr}");

    assert!(
        logs.contains("Found env var GUARDY_SCAN_SENSITIVE (type: bool): 1"),
        "GUARDY_SCAN_SENSITIVE should be parsed. Output: {logs}"
    );

    assert!(
        logs.contains("sensitive: true"),
        "GUARDY_SCAN_SENSITIVE should override default. Output: {logs}"
    );

    // Test 3: Multiple boolean env vars should work together
    let output = Command::new(&guardy_binary)
        .args(["scan", "/dev/null"])
        .env("GUARDY_SCAN_SHOW", "1")
        .env("GUARDY_SCAN_SENSITIVE", "1")
        .env("RUST_LOG", "debug")
        .output()
        .expect("Failed to execute guardy");

    let stderr = String::from_utf8_lossy(&output.stderr);
    let stdout = String::from_utf8_lossy(&output.stdout);
    let logs = format!("{stdout}{stderr}");

    assert!(
        logs.contains("show: true") && logs.contains("sensitive: true"),
        "Both environment variables should work together. Output: {logs}"
    );
}

/// Test that environment variables support various truthy/falsy values
#[test]
fn test_env_var_boolean_parsing() {
    // Try multiple possible locations for the binary
    let guardy_binary = env::var("CARGO_BIN_EXE_guardy")
        .or_else(|_| {
            // Try relative paths from test directory
            let paths = [
                "target/release/guardy",
                "target/debug/guardy",
                "../../target/release/guardy",
                "../../target/debug/guardy",
            ];
            for path in &paths {
                if std::path::Path::new(path).exists() {
                    return Ok(path.to_string());
                }
            }
            Err("Could not find guardy binary")
        })
        .expect("Failed to locate guardy binary for testing");

    // Test different truthy values
    for truthy_val in ["1", "true", "TRUE", "yes", "YES", "on", "ON"] {
        let output = Command::new(&guardy_binary)
            .args(["scan", "/dev/null"])
            .env("GUARDY_SCAN_SHOW", truthy_val)
            .env("RUST_LOG", "debug")
            .output()
            .expect("Failed to execute guardy");

        let stderr = String::from_utf8_lossy(&output.stderr);
        let stdout = String::from_utf8_lossy(&output.stdout);
        let logs = format!("{stdout}{stderr}");
        assert!(
            logs.contains("show: true"),
            "Value '{truthy_val}' should be parsed as true. Output: {logs}"
        );
    }

    // Test different falsy values
    for falsy_val in ["0", "false", "FALSE", "no", "NO", "off", "OFF"] {
        let output = Command::new(&guardy_binary)
            .args(["scan", "/dev/null"])
            .env("GUARDY_SCAN_SHOW", falsy_val)
            .env("RUST_LOG", "debug")
            .output()
            .expect("Failed to execute guardy");

        let stderr = String::from_utf8_lossy(&output.stderr);
        let stdout = String::from_utf8_lossy(&output.stdout);
        let logs = format!("{stdout}{stderr}");
        assert!(
            logs.contains("show: false"),
            "Value '{falsy_val}' should be parsed as false. Output: {logs}"
        );
    }
}

/// Test that non-boolean environment variables still work with CLI precedence
#[test]
fn test_env_var_non_boolean_precedence() {
    // Try multiple possible locations for the binary
    let guardy_binary = env::var("CARGO_BIN_EXE_guardy")
        .or_else(|_| {
            // Try relative paths from test directory
            let paths = [
                "target/release/guardy",
                "target/debug/guardy",
                "../../target/release/guardy",
                "../../target/debug/guardy",
            ];
            for path in &paths {
                if std::path::Path::new(path).exists() {
                    return Ok(path.to_string());
                }
            }
            Err("Could not find guardy binary")
        })
        .expect("Failed to locate guardy binary for testing");

    // Test max_file_size_mb environment variable (non-boolean)
    let output = Command::new(&guardy_binary)
        .args(["scan", "/dev/null"])
        .env("GUARDY_SCAN_MAX_SIZE", "50")
        .env("RUST_LOG", "debug")
        .output()
        .expect("Failed to execute guardy");

    let stderr = String::from_utf8_lossy(&output.stderr);
    let stdout = String::from_utf8_lossy(&output.stdout);
    let logs = format!("{stdout}{stderr}");

    // Should find and parse the environment variable
    assert!(
        logs.contains("Found env var GUARDY_SCAN_MAX_SIZE"),
        "Non-boolean environment variable should be parsed. Output: {logs}"
    );
}