use std::{collections::HashMap, sync::Arc};
use guardy::{
config::hooks::{HookCommand, HookCondition, HookDefinition, HookScript},
hooks::conditions::{ConditionEvaluator, should_skip},
};
#[tokio::test]
async fn test_executor_basic_command() {
let mut commands = HashMap::new();
let test_command = HookCommand {
run: "echo 'Test Output'".to_string(),
description: "Test echo command".to_string(),
..Default::default()
};
assert_eq!(test_command.run, "echo 'Test Output'");
assert_eq!(test_command.description, "Test echo command");
assert!(!test_command.continue_on_error);
assert!(!test_command.interactive);
commands.insert("test-echo".to_string(), test_command);
let hook = HookDefinition {
parallel: false,
skip: false,
commands,
scripts: HashMap::new(),
..Default::default()
};
assert!(!hook.parallel);
assert!(!hook.skip);
assert_eq!(hook.commands.len(), 1);
assert!(hook.commands.contains_key("test-echo"));
println!("✅ Basic command execution structure validated");
}
#[tokio::test]
async fn test_condition_evaluation() {
let evaluator = ConditionEvaluator::new();
let bool_true = HookCondition::Bool(true);
let bool_false = HookCondition::Bool(false);
assert!(evaluator.evaluate(&bool_true).unwrap());
assert!(!evaluator.evaluate(&bool_false).unwrap());
let always_true = HookCondition::Array(vec!["true".to_string()]);
let always_false = HookCondition::Array(vec!["false".to_string()]);
let mixed = HookCondition::Array(vec!["false".to_string(), "true".to_string()]);
assert!(evaluator.evaluate(&always_true).unwrap());
assert!(!evaluator.evaluate(&always_false).unwrap());
assert!(evaluator.evaluate(&mixed).unwrap());
println!("✅ Condition evaluation tests passed");
}
#[tokio::test]
async fn test_should_skip_logic() {
let evaluator = ConditionEvaluator::new();
let skip_true = HookCondition::Bool(true);
let only_false = HookCondition::Bool(false);
assert!(should_skip(&skip_true, &only_false, &evaluator).unwrap());
let skip_false = HookCondition::Bool(false);
assert!(!should_skip(&skip_false, &only_false, &evaluator).unwrap());
let only_conditions = HookCondition::Array(vec!["branch:nonexistent".to_string()]);
assert!(should_skip(&skip_false, &only_conditions, &evaluator).unwrap());
let only_true = HookCondition::Bool(true);
assert!(!should_skip(&skip_false, &only_true, &evaluator).unwrap());
println!("✅ Should skip logic tests passed");
}
#[tokio::test]
async fn test_priority_based_execution() {
let mut commands = HashMap::new();
let high_priority = HookCommand {
run: "echo 'High Priority'".to_string(),
description: "High priority command".to_string(),
priority: -10, ..Default::default()
};
let medium_priority = HookCommand {
run: "echo 'Medium Priority'".to_string(),
description: "Medium priority command".to_string(),
..Default::default()
};
let low_priority = HookCommand {
run: "echo 'Low Priority'".to_string(),
description: "Low priority command".to_string(),
priority: 10, continue_on_error: false,
all_files: false,
glob: Arc::new(vec![]),
file_types: vec![],
stage_fixed: false,
skip: HookCondition::Bool(false),
only: HookCondition::Bool(false),
env: HashMap::new(),
root: None,
exclude: vec![],
fail_text: None,
interactive: false,
use_stdin: false,
tags: vec![],
files: None,
};
assert_eq!(high_priority.priority, -10);
assert_eq!(medium_priority.priority, 0);
assert_eq!(low_priority.priority, 10);
let mut priorities = vec![
low_priority.priority,
high_priority.priority,
medium_priority.priority,
];
priorities.sort();
assert_eq!(priorities, vec![-10, 0, 10]);
commands.insert("high-priority".to_string(), high_priority);
commands.insert("medium-priority".to_string(), medium_priority);
commands.insert("low-priority".to_string(), low_priority);
let hook = HookDefinition {
parallel: false,
skip: false,
commands,
scripts: HashMap::new(),
..Default::default()
};
assert_eq!(hook.commands.len(), 3);
assert!(hook.commands.contains_key("high-priority"));
assert!(hook.commands.contains_key("medium-priority"));
assert!(hook.commands.contains_key("low-priority"));
println!("✅ Priority-based execution ordering validated");
}
#[tokio::test]
async fn test_file_glob_patterns() {
let command = HookCommand {
run: "echo 'Processing files'".to_string(),
description: "Test glob patterns".to_string(),
priority: 0,
continue_on_error: false,
all_files: false,
glob: Arc::new(vec!["*.rs".to_string(), "*.toml".to_string()]),
file_types: vec![],
stage_fixed: false,
skip: HookCondition::Bool(false),
only: HookCondition::Bool(false),
env: HashMap::new(),
root: None,
exclude: vec!["target/**".to_string()],
fail_text: None,
interactive: false,
use_stdin: false,
tags: vec![],
files: None,
};
assert_eq!(command.glob.len(), 2);
assert!(command.glob.contains(&"*.rs".to_string()));
assert!(command.glob.contains(&"*.toml".to_string()));
assert_eq!(command.exclude.len(), 1);
assert!(command.exclude.contains(&"target/**".to_string()));
println!("✅ File glob pattern tests passed");
}
#[tokio::test]
async fn test_environment_variables() {
let mut env = HashMap::new();
env.insert("TEST_VAR".to_string(), "test_value".to_string());
env.insert("ANOTHER_VAR".to_string(), "another_value".to_string());
let command = HookCommand {
run: "echo $TEST_VAR".to_string(),
description: "Test environment variables".to_string(),
priority: 0,
continue_on_error: false,
all_files: false,
glob: Arc::new(vec![]),
file_types: vec![],
stage_fixed: false,
skip: HookCondition::Bool(false),
only: HookCondition::Bool(false),
env: env.clone(),
root: None,
exclude: vec![],
fail_text: None,
interactive: false,
use_stdin: false,
tags: vec![],
files: None,
};
assert_eq!(command.env.len(), 2);
assert_eq!(command.env.get("TEST_VAR"), Some(&"test_value".to_string()));
assert_eq!(
command.env.get("ANOTHER_VAR"),
Some(&"another_value".to_string())
);
println!("✅ Environment variable tests passed");
}
#[tokio::test]
async fn test_script_configuration() {
let mut scripts = HashMap::new();
let mut env = HashMap::new();
env.insert("NODE_ENV".to_string(), "test".to_string());
scripts.insert(
"test-script.js".to_string(),
HookScript {
runner: "node".to_string(),
env: env.clone(),
},
);
scripts.insert(
"test-script.py".to_string(),
HookScript {
runner: "python3".to_string(),
env: HashMap::new(),
},
);
let hook = HookDefinition {
parallel: false,
skip: false,
commands: HashMap::new(),
scripts,
..Default::default()
};
assert_eq!(hook.scripts.len(), 2);
assert!(hook.scripts.contains_key("test-script.js"));
assert!(hook.scripts.contains_key("test-script.py"));
let node_script = hook.scripts.get("test-script.js").unwrap();
assert_eq!(node_script.runner, "node");
assert_eq!(node_script.env.get("NODE_ENV"), Some(&"test".to_string()));
let python_script = hook.scripts.get("test-script.py").unwrap();
assert_eq!(python_script.runner, "python3");
assert!(python_script.env.is_empty());
println!("✅ Script configuration tests passed");
}
#[tokio::test]
async fn test_interactive_and_stdin_modes() {
let interactive_command = HookCommand {
run: "interactive_command".to_string(),
description: "Interactive command".to_string(),
priority: 0,
continue_on_error: false,
all_files: false,
glob: Arc::new(vec![]),
file_types: vec![],
stage_fixed: false,
skip: HookCondition::Bool(false),
only: HookCondition::Bool(false),
env: HashMap::new(),
root: None,
exclude: vec![],
fail_text: None,
interactive: true,
use_stdin: false,
tags: vec![],
files: None,
};
let stdin_command = HookCommand {
run: "stdin_command".to_string(),
description: "Stdin command".to_string(),
priority: 0,
continue_on_error: false,
all_files: false,
glob: Arc::new(vec![]),
file_types: vec![],
stage_fixed: false,
skip: HookCondition::Bool(false),
only: HookCondition::Bool(false),
env: HashMap::new(),
root: None,
exclude: vec![],
fail_text: None,
interactive: false,
use_stdin: true,
tags: vec![],
files: None,
};
assert!(interactive_command.interactive);
assert!(!interactive_command.use_stdin);
assert!(!stdin_command.interactive);
assert!(stdin_command.use_stdin);
println!("✅ Interactive and stdin mode tests passed");
}
#[tokio::test]
async fn test_fail_text_and_continue_on_error() {
let command_with_fail_text = HookCommand {
run: "failing_command".to_string(),
description: "Command that might fail".to_string(),
priority: 0,
continue_on_error: true,
all_files: false,
glob: Arc::new(vec![]),
file_types: vec![],
stage_fixed: false,
skip: HookCondition::Bool(false),
only: HookCondition::Bool(false),
env: HashMap::new(),
root: None,
exclude: vec![],
fail_text: Some("Custom failure message".to_string()),
interactive: false,
use_stdin: false,
tags: vec![],
files: None,
};
assert!(command_with_fail_text.continue_on_error);
assert_eq!(
command_with_fail_text.fail_text,
Some("Custom failure message".to_string())
);
println!("✅ Fail text and continue on error tests passed");
}
#[tokio::test]
async fn test_tags_and_categorization() {
let tagged_command = HookCommand {
run: "tagged_command".to_string(),
description: "Tagged command".to_string(),
tags: vec![
"linting".to_string(),
"formatting".to_string(),
"quick".to_string(),
],
..Default::default()
};
assert_eq!(tagged_command.tags.len(), 3);
assert!(tagged_command.tags.contains(&"linting".to_string()));
assert!(tagged_command.tags.contains(&"formatting".to_string()));
assert!(tagged_command.tags.contains(&"quick".to_string()));
println!("✅ Tags and categorization tests passed");
}
#[tokio::test]
async fn test_working_directory_root() {
let command_with_root = HookCommand {
run: "command_in_subdir".to_string(),
description: "Command with root directory".to_string(),
priority: 0,
continue_on_error: false,
all_files: false,
glob: Arc::new(vec![]),
file_types: vec![],
stage_fixed: false,
skip: HookCondition::Bool(false),
only: HookCondition::Bool(false),
env: HashMap::new(),
root: Some("./packages/guardy".to_string()),
exclude: vec![],
fail_text: None,
interactive: false,
use_stdin: false,
tags: vec![],
files: None,
};
assert_eq!(
command_with_root.root,
Some("./packages/guardy".to_string())
);
println!("✅ Working directory root tests passed");
}
#[test]
fn test_parallel_execution_configuration() {
let parallel_hook = HookDefinition {
parallel: true,
skip: false,
commands: HashMap::new(),
scripts: HashMap::new(),
..Default::default()
};
let sequential_hook = HookDefinition {
parallel: false,
skip: false,
commands: HashMap::new(),
scripts: HashMap::new(),
..Default::default()
};
assert!(parallel_hook.parallel);
assert!(!sequential_hook.parallel);
println!("✅ Parallel execution configuration tests passed");
}
#[tokio::test]
async fn test_file_placeholder_substitution() {
let command_with_placeholders = HookCommand {
run: "echo 'Files: {files}' && echo 'Staged: {staged_files}' && echo 'All: {all_files}'"
.to_string(),
description: "Test file placeholder substitution".to_string(),
glob: Arc::new(vec!["*.rs".to_string()]),
exclude: vec!["target/**".to_string()],
tags: vec!["file-processing".to_string()],
..Default::default()
};
assert!(command_with_placeholders.run.contains("{files}"));
assert!(command_with_placeholders.run.contains("{staged_files}"));
assert!(command_with_placeholders.run.contains("{all_files}"));
assert_eq!(command_with_placeholders.glob.len(), 1);
assert!(command_with_placeholders.glob.contains(&"*.rs".to_string()));
assert_eq!(command_with_placeholders.exclude.len(), 1);
assert!(
command_with_placeholders
.exclude
.contains(&"target/**".to_string())
);
println!("✅ File placeholder substitution test structure validated");
}
#[tokio::test]
async fn test_push_files_placeholder() {
let push_command = HookCommand {
run: "echo 'Push files: {push_files}'".to_string(),
description: "Test push files placeholder".to_string(),
tags: vec!["pre-push".to_string()],
..Default::default()
};
assert!(push_command.run.contains("{push_files}"));
println!("✅ Push files placeholder test structure validated");
}
#[tokio::test]
async fn test_stage_fixed_functionality() {
let formatting_command = HookCommand {
run: "cargo fmt {files}".to_string(),
description: "Format Rust files and stage changes".to_string(),
glob: Arc::new(vec!["*.rs".to_string()]),
stage_fixed: true, exclude: vec!["target/**".to_string()],
tags: vec!["formatting".to_string()],
..Default::default()
};
assert!(formatting_command.stage_fixed);
assert!(formatting_command.run.contains("{files}"));
assert!(formatting_command.glob.contains(&"*.rs".to_string()));
println!("✅ Stage fixed functionality test structure validated");
}