use ggen_utils::error::{Context, Error, Result};
use ggen_utils::safe_command::{CommandArg, CommandName, SafeCommand};
use ggen_utils::safe_path::SafePath;
use std::error::Error as StdError;
use std::io;
use std::path::PathBuf;
#[test]
fn test_error_creation_simple() {
let message = "Something went wrong";
let error = Error::new(message);
let display = format!("{}", error);
assert_eq!(display, message);
}
#[test]
fn test_error_creation_with_context() {
let message = "Failed to read file";
let context = "config.toml";
let error = Error::with_context(message, context);
let display = format!("{}", error);
assert!(display.contains(message));
assert!(display.contains(context));
}
#[test]
fn test_error_creation_with_source() {
let message = "Configuration error";
let source_error = io::Error::new(io::ErrorKind::NotFound, "File not found");
let error = Error::with_source(message, Box::new(source_error));
let display = format!("{}", error);
assert!(display.contains(message));
assert!(display.contains("File not found"));
}
#[test]
fn test_error_context_method_creates_chain() {
let original_error = Error::new("Database connection failed");
let chained_error = original_error.context("While processing user request");
let display = format!("{}", chained_error);
assert!(display.contains("While processing user request"));
assert!(display.contains("Database connection failed"));
assert!(StdError::source(&chained_error).is_some());
}
#[test]
fn test_error_with_context_fn_creates_chain() {
let original_error = Error::new("Parse error");
let step_number = 42;
let chained_error =
original_error.with_context_fn(|| format!("Failed at step {}", step_number));
let display = format!("{}", chained_error);
assert!(display.contains("Failed at step 42"));
assert!(display.contains("Parse error"));
}
#[test]
fn test_error_source_chain_multiple_levels() {
let io_error = io::Error::new(io::ErrorKind::PermissionDenied, "Access denied");
let level1 = Error::with_source("File operation failed", Box::new(io_error));
let level2 = level1.context("During configuration load");
let level3 = level2.context("Application startup failed");
let display = format!("{}", level3);
assert!(display.contains("Application startup failed"));
assert!(display.contains("During configuration load"));
assert!(display.contains("File operation failed"));
let mut current_source = StdError::source(&level3);
let mut chain_depth = 0;
while current_source.is_some() {
chain_depth += 1;
let error_ref = current_source.unwrap();
current_source = StdError::source(error_ref);
}
assert_eq!(chain_depth, 3, "Chain should have exactly 3 levels");
}
#[test]
fn test_error_source_returns_none_for_simple_error() {
let error = Error::new("Simple error without source");
let source = StdError::source(&error);
assert!(source.is_none());
}
#[test]
fn test_from_io_error() {
let io_error = io::Error::new(io::ErrorKind::NotFound, "config.toml");
let error: Error = io_error.into();
let display = format!("{}", error);
assert!(display.contains("config.toml"));
}
#[test]
fn test_from_string() {
let error_msg = String::from("Dynamic error message");
let error: Error = error_msg.clone().into();
let display = format!("{}", error);
assert_eq!(display, error_msg);
}
#[test]
fn test_from_str() {
let error_msg: &str = "Static error message";
let error: Error = error_msg.into();
let display = format!("{}", error);
assert_eq!(display, error_msg);
}
#[test]
fn test_result_context_trait_on_ok() {
let ok_result: Result<String> = Ok("success".to_string());
let result = ok_result.context("This should not appear");
assert!(result.is_ok());
assert_eq!(result.unwrap(), "success");
}
#[test]
fn test_result_context_trait_on_err() {
let err_result: Result<()> = Err(Error::new("Base error"));
let result = err_result.context("Additional context");
assert!(result.is_err());
let error = result.unwrap_err();
let display = format!("{}", error);
assert!(display.contains("Additional context"));
}
#[test]
fn test_safe_command_whitelist_blocks_dangerous_commands() {
let dangerous_commands = vec![
"rm", "rmdir", "mv", "dd", "mkfs", "kill", "killall", "pkill", "sudo", "su", "chmod",
"chown", "curl", "wget", "nc", "netcat", "telnet", "ssh", "scp", "rsync", "tar", "zip",
"unzip", "7z",
];
for cmd in dangerous_commands {
let result = CommandName::new(cmd);
assert!(result.is_err(), "Should block dangerous command: {}", cmd);
let error_msg = result.unwrap_err().to_string();
assert!(
error_msg.contains("not in whitelist") || error_msg.contains("whitelist"),
"Error should mention whitelist for: {}",
cmd
);
}
assert!(CommandName::new("sh").is_ok(), "sh should be whitelisted");
assert!(
CommandName::new("bash").is_ok(),
"bash should be whitelisted"
);
}
#[test]
fn test_safe_command_shell_metacharacters_blocked() {
let injection_attempts = vec![
("build; rm -rf /", ';'),
("build | tee output", '|'),
("build && rm -rf /", '&'),
("build > /etc/passwd", '>'),
("build < input", '<'),
("$(whoami)", '$'),
("`whoami`", '`'),
("build\nrm -rf /", '\n'),
("build\rrm -rf /", '\r'),
("build || echo hacked", '|'),
];
for (attack, metachar) in injection_attempts {
let result = CommandArg::new(attack);
assert!(
result.is_err(),
"Should block shell metacharacter '{}': {}",
metachar,
attack
);
let error_msg = result.unwrap_err().to_string();
assert!(
error_msg.contains("metacharacter"),
"Error should mention metacharacter for: {}",
attack
);
}
}
#[test]
fn test_safe_command_length_limits_enforced() {
let long_arg = "a".repeat(5000);
let result = SafeCommand::new("cargo")
.unwrap()
.arg(&long_arg)
.unwrap()
.validate();
assert!(result.is_err(), "Should block command exceeding max length");
let error_msg = result.unwrap_err().to_string();
assert!(
error_msg.contains("exceeds maximum") || error_msg.contains("length"),
"Error should mention length limit"
);
}
#[test]
fn test_safe_path_traversal_prevention() {
let traversal_attempts = vec![
"../../../etc/passwd",
"../../etc/passwd",
"../etc/passwd",
"subdir/../../etc/passwd",
"./../../etc/passwd",
"safe/../../../etc/passwd",
"../../../../../../etc/passwd",
];
for attack in traversal_attempts {
let result = SafePath::new(attack);
assert!(result.is_err(), "Should block path traversal: {}", attack);
let error_msg = result.unwrap_err().to_string();
assert!(
error_msg.contains("parent directory") || error_msg.contains(".."),
"Error should mention parent directory for: {}",
attack
);
}
}
#[test]
fn test_safe_path_empty_and_invalid_blocked() {
let invalid_paths = vec![
"",
" ",
"\t",
"\n",
"path/ /file", ];
for invalid_path in invalid_paths {
let result = SafePath::new(invalid_path);
assert!(
result.is_err(),
"Should block invalid path: {:?}",
invalid_path
);
let error_msg = result.unwrap_err().to_string();
assert!(
error_msg.contains("empty")
|| error_msg.contains("whitespace")
|| error_msg.contains("Invalid"),
"Error should describe the issue for: {:?}",
invalid_path
);
}
}
#[test]
fn test_safe_path_depth_limit_enforced() {
let deep_components: Vec<String> = (0..=20).map(|i| format!("level{}", i)).collect();
let deep_path = deep_components.join("/");
let result = SafePath::new(&deep_path);
assert!(result.is_err(), "Should block path exceeding max depth");
let error_msg = result.unwrap_err().to_string();
assert!(
error_msg.contains("depth") || error_msg.contains("exceeds"),
"Error should mention depth limit"
);
}
#[test]
fn test_error_invalid_input_helper() {
let input = "invalid-value-123";
let error = Error::invalid_input(input);
let display = format!("{}", error);
assert!(display.contains("Invalid input"));
assert!(display.contains(input));
}
#[test]
fn test_error_network_error_helper() {
let network_msg = "Connection timeout after 30s";
let error = Error::network_error(network_msg);
let display = format!("{}", error);
assert!(display.contains("Network error"));
assert!(display.contains(network_msg));
}
#[test]
fn test_error_file_not_found_helper() {
let path = PathBuf::from("/nonexistent/config.toml");
let error = Error::file_not_found(path.clone());
let display = format!("{}", error);
assert!(display.contains("File not found"));
assert!(display.contains("config.toml"));
}
#[test]
fn test_safe_command_with_safe_path_integration() {
let safe_path = SafePath::new("src/main.rs").unwrap();
let cmd_result = SafeCommand::new("rustfmt")
.unwrap()
.arg_path(&safe_path)
.validate();
assert!(
cmd_result.is_ok(),
"SafePath should integrate with SafeCommand"
);
let cmd = cmd_result.unwrap();
let cmd_string = cmd.to_string_debug();
assert!(cmd_string.contains("rustfmt"));
assert!(cmd_string.contains("src/main.rs"));
}
#[test]
fn test_error_chain_from_real_operation() {
let non_existent_file = PathBuf::from("/tmp/does_not_exist_xyz123.txt");
let read_result: Result<String> = std::fs::read_to_string(&non_existent_file)
.map_err(|e| Error::with_source("Failed to read configuration", Box::new(e)))
.context("During application initialization");
assert!(read_result.is_err());
let error = read_result.unwrap_err();
let display = format!("{}", error);
assert!(display.contains("application initialization"));
assert!(display.contains("configuration"));
}
#[test]
fn test_multiple_validation_layers() {
let dangerous_input = "../../../etc/passwd; rm -rf /";
let path_result = SafePath::new(dangerous_input);
assert!(path_result.is_err());
let arg_result = CommandArg::new(dangerous_input);
assert!(
arg_result.is_err(),
"Should also fail on metacharacter check"
);
}
#[test]
fn test_command_injection_prevention_comprehensive() {
let injection_vectors = vec![
"arg; malicious",
"arg | malicious",
"arg && malicious",
"arg || malicious",
"arg `malicious`",
"arg $(malicious)",
"arg > file",
"arg < file",
"arg >> file",
"arg 2>&1",
"arg\nmalicious",
"arg\rmalicious",
"arg\r\nmalicious",
"arg;${malicious}",
"arg;`malicious`",
"arg|malicious",
"arg||malicious",
];
for injection in injection_vectors {
let result = CommandArg::new(injection);
assert!(result.is_err(), "Should block injection: {}", injection);
}
}
#[test]
fn test_path_traversal_edge_cases() {
let edge_cases = vec![
"./../etc/passwd",
"dir/.../etc/passwd",
"dir/....",
"dir/.../file",
"..",
"../",
"./..",
"../.",
"file/../etc/passwd",
];
for edge_case in edge_cases {
let result = SafePath::new(edge_case);
let _ = result;
}
}
#[test]
fn test_error_preserves_all_information() {
let source = io::Error::new(io::ErrorKind::InvalidData, "Corrupt data");
let error = Error::with_source("Data processing failed", Box::new(source))
.context("While parsing user input")
.context("During request handling");
let display = format!("{}", error);
assert!(display.contains("request handling"));
assert!(display.contains("user input"));
assert!(display.contains("Data processing"));
assert!(display.contains("Corrupt data"));
}
#[test]
fn test_result_type_with_context() {
fn failing_function() -> Result<String> {
Err(Error::new("Base failure"))
}
let result = failing_function().context("In high_level_function");
assert!(result.is_err());
let error = result.unwrap_err();
let display = format!("{}", error);
assert!(display.contains("high_level_function"));
assert!(display.contains("Base failure"));
}
#[test]
fn test_result_type_with_context_closure() {
let attempt_number = 3;
fn operation(n: usize) -> Result<()> {
if n < 5 {
Err(Error::new("Not enough attempts"))
} else {
Ok(())
}
}
let result =
operation(attempt_number).with_context(|| format!("Failed on attempt {}", attempt_number));
assert!(result.is_err());
let error = result.unwrap_err();
let display = format!("{}", error);
assert!(display.contains("attempt 3"));
}
#[test]
fn test_result_question_mark_operator() {
fn validate(value: i32) -> Result<i32> {
if value < 0 {
Err(Error::invalid_input("Value must be non-negative"))
} else {
Ok(value * 2)
}
}
fn process(value: i32) -> Result<i32> {
let validated = validate(value)?; Ok(validated + 10)
}
assert!(process(-5).is_err());
assert_eq!(process(5).unwrap(), 20); }