guardy 0.2.4

Fast, secure git hooks in Rust with secret scanning and protected file synchronization
Documentation
use std::fs;

use guardy::config::{
    core::CONFIG,
    files::loader::ConfigFileLoader,
    hooks::{HookCommand, HookDefinition},
};
use tempfile::TempDir;

#[tokio::test]
async fn test_hooks_config_loading_from_global_config() {
    // Test that the hooks configuration loads properly from the global CONFIG
    let hooks_config = &CONFIG.hooks;

    // Test that global settings have defaults
    assert!(!hooks_config.skip_all);
    assert!(hooks_config.parallel);

    // Test that hook definitions are loaded (should be defaults if no file)
    assert!(!hooks_config.pre_commit.skip);
    assert!(!hooks_config.commit_msg.skip);

    println!("✅ Global hooks config loaded successfully");
}

#[tokio::test]
async fn test_hooks_config_loading_via_config_loader() {
    // Test loading configuration through the REAL config loading system
    // This tests the actual production code path that users will experience

    let temp_dir = TempDir::new().unwrap();
    let temp_path = temp_dir.path();

    // Create .git directory to act as git root boundary
    fs::create_dir(temp_path.join(".git")).unwrap();

    // Create a test config file with BOTH dash and underscore formats to test flexibility
    let config_with_dashes = r#"
[hooks]
parallel = true
skip-all = false

[hooks.pre-commit]
parallel = true
skip = false

[hooks.pre-commit.commands.rust-format]
run = "cargo fmt --check"
glob = ["*.rs"]
stage-fixed = true
description = "Format Rust code with dashes"
"#;

    fs::write(temp_path.join("guardy.toml"), config_with_dashes).unwrap();

    // Load through the real config system using explicit directory
    let loader = ConfigFileLoader::new("guardy");
    let merged_config: Option<serde_json::Value> =
        loader.discover_and_load_merged_in(temp_path).unwrap();

    assert!(merged_config.is_some(), "Should load config file");
    let config = merged_config.unwrap();

    // Verify the hooks section loaded correctly through flexible key lookup
    let hooks_section = config.get("hooks").expect("Should have hooks section");

    // Test that get_flexible_key works - it should find pre-commit even though file uses dashes
    let pre_commit = guardy::config::macros::get_flexible_key::<HookDefinition>(
        hooks_section,
        "pre_commit", // Using underscore - should still find pre-commit in file
    );

    assert!(
        pre_commit.is_some(),
        "Should find pre_commit/pre-commit via flexible lookup"
    );
    let pre_commit = pre_commit.unwrap();
    assert!(pre_commit.parallel);
    assert!(!pre_commit.skip);
    assert_eq!(pre_commit.commands.len(), 1);

    let rust_format = pre_commit
        .commands
        .get("rust-format")
        .expect("Should have rust-format command");
    assert_eq!(rust_format.run, "cargo fmt --check");
    assert!(
        rust_format.stage_fixed,
        "Should parse stage-fixed with kebab-case"
    );
    assert_eq!(rust_format.description, "Format Rust code with dashes");

    println!("✅ Config loaded via real config system with flexible key lookup");
}

#[tokio::test]
async fn test_flexible_config_formats() {
    // Test that both underscore and dash formats work in config files
    let temp_dir = TempDir::new().unwrap();
    let temp_path = temp_dir.path();

    // Create .git directory to act as git root boundary
    fs::create_dir(temp_path.join(".git")).unwrap();

    // Test with underscores (traditional TOML style)
    let config_with_underscores = r#"
[hooks]
[hooks.pre_commit]
parallel = true

[hooks.pre_commit.commands.test]
run = "echo test"
stage_fixed = true
"#;

    fs::write(temp_path.join("guardy.toml"), config_with_underscores).unwrap();

    let loader = ConfigFileLoader::new("guardy");
    let merged_config: Option<serde_json::Value> =
        loader.discover_and_load_merged_in(temp_path).unwrap();

    assert!(merged_config.is_some());
    let config = merged_config.unwrap();
    let hooks = config.get("hooks").unwrap();

    // Should work with both lookup styles due to flexible key
    let pre_commit_underscore =
        guardy::config::macros::get_flexible_key::<HookDefinition>(hooks, "pre_commit");
    let pre_commit_dash =
        guardy::config::macros::get_flexible_key::<HookDefinition>(hooks, "pre-commit");

    assert!(
        pre_commit_underscore.is_some() || pre_commit_dash.is_some(),
        "Should find hook with either underscore or dash lookup"
    );

    println!("✅ Both underscore and dash formats work correctly");
}

#[test]
fn test_hook_command_defaults() {
    // Test that HookCommand has proper defaults
    let cmd: HookCommand = serde_json::from_str(r#"{"run": "echo test"}"#)
        .expect("Failed to deserialize minimal HookCommand");

    assert_eq!(cmd.run, "echo test");
    assert_eq!(cmd.description, "");
    assert!(!cmd.continue_on_error);
    assert!(!cmd.all_files);
    assert!(!cmd.stage_fixed);
    assert!(cmd.glob.is_empty());
    assert!(cmd.file_types.is_empty());
}

#[test]
fn test_hook_definition_defaults() {
    // Test that HookDefinition has proper defaults
    let hook: HookDefinition =
        serde_json::from_str(r#"{}"#).expect("Failed to deserialize empty HookDefinition");

    assert!(!hook.parallel);
    assert!(!hook.skip);
    assert!(hook.commands.is_empty());
    assert!(hook.scripts.is_empty());
}