use std::path::Path;
use super::cache::get_trusted_info;
use super::hash::compute_file_hash;
use super::types::{ConfirmationReason, TrustStatus};
use crate::config::Config;
pub fn verify_trust(
repo_root: &Path,
config: &Config,
has_project_hooks: bool,
project_config_path: Option<&Path>,
) -> TrustStatus {
let commands = match config.post_create_commands() {
Some(cmds) if !cmds.is_empty() => cmds.to_vec(),
_ => return TrustStatus::NoHooks,
};
if !has_project_hooks {
return TrustStatus::GlobalConfig;
}
let config_path = match project_config_path {
Some(path) if path.exists() => path,
Some(path) => {
return TrustStatus::NeedsConfirmation {
reason: ConfirmationReason::FirstTime,
commands,
config_path: path.to_path_buf(),
config_hash: String::new(),
};
}
None => {
return TrustStatus::NeedsConfirmation {
reason: ConfirmationReason::FirstTime,
commands,
config_path: repo_root.to_path_buf(),
config_hash: String::new(),
};
}
};
let current_hash = match compute_file_hash(config_path) {
Ok(hash) => hash,
Err(_) => {
return TrustStatus::NeedsConfirmation {
reason: ConfirmationReason::FirstTime,
commands,
config_path: config_path.to_path_buf(),
config_hash: String::new(),
}
}
};
let trusted_info = get_trusted_info(repo_root);
match trusted_info {
None => TrustStatus::NeedsConfirmation {
reason: ConfirmationReason::FirstTime,
commands,
config_path: config_path.to_path_buf(),
config_hash: current_hash,
},
Some(info) if info.config_hash != current_hash => TrustStatus::NeedsConfirmation {
reason: ConfirmationReason::ConfigChanged,
commands,
config_path: config_path.to_path_buf(),
config_hash: current_hash,
},
Some(_) => TrustStatus::Trusted,
}
}
#[cfg(test)]
mod tests {
use super::*;
use tempfile::TempDir;
fn create_config_with_hooks() -> Config {
let mut config = Config::default();
config.hooks = Some(crate::config::HooksConfig {
post_create: Some(crate::config::HookConfig {
enabled: true,
commands: vec!["npm install".to_string()],
}),
});
config
}
#[test]
fn test_verify_trust_no_hooks() {
let config = Config::default();
let status = verify_trust(Path::new("/repo"), &config, false, None);
assert!(matches!(status, TrustStatus::NoHooks));
}
#[test]
fn test_verify_trust_global_only() {
let config = create_config_with_hooks();
let status = verify_trust(Path::new("/repo"), &config, false, None);
assert!(matches!(status, TrustStatus::GlobalConfig));
}
#[test]
fn test_verify_trust_project_hooks_no_config_path() {
let config = create_config_with_hooks();
let status = verify_trust(Path::new("/repo"), &config, true, None);
match status {
TrustStatus::NeedsConfirmation {
reason,
commands,
config_path,
config_hash,
} => {
assert_eq!(reason, ConfirmationReason::FirstTime);
assert_eq!(commands, vec!["npm install".to_string()]);
assert_eq!(config_path, Path::new("/repo"));
assert!(config_hash.is_empty());
}
_ => panic!("Expected NeedsConfirmation, got {:?}", status),
}
}
#[test]
fn test_verify_trust_config_path_not_exist() {
let config = create_config_with_hooks();
let nonexistent_path = Path::new("/nonexistent/config.toml");
let status = verify_trust(Path::new("/repo"), &config, true, Some(nonexistent_path));
match status {
TrustStatus::NeedsConfirmation {
reason,
commands,
config_path,
config_hash,
} => {
assert_eq!(reason, ConfirmationReason::FirstTime);
assert_eq!(commands, vec!["npm install".to_string()]);
assert_eq!(config_path, nonexistent_path);
assert!(config_hash.is_empty());
}
_ => panic!("Expected NeedsConfirmation, got {:?}", status),
}
}
#[test]
fn test_verify_trust_first_time_with_valid_config() {
let temp_dir = TempDir::new().unwrap();
let config_path = temp_dir.path().join("config.toml");
std::fs::write(&config_path, "[hooks]\npost_create = []").unwrap();
let config = create_config_with_hooks();
let status = verify_trust(temp_dir.path(), &config, true, Some(&config_path));
match status {
TrustStatus::NeedsConfirmation {
reason,
commands,
config_path: returned_path,
config_hash,
} => {
assert_eq!(reason, ConfirmationReason::FirstTime);
assert_eq!(commands, vec!["npm install".to_string()]);
assert_eq!(returned_path, config_path);
assert!(!config_hash.is_empty()); }
_ => panic!("Expected NeedsConfirmation(FirstTime), got {:?}", status),
}
}
#[test]
fn test_verify_trust_commands_included() {
let mut config = Config::default();
config.hooks = Some(crate::config::HooksConfig {
post_create: Some(crate::config::HookConfig {
enabled: true,
commands: vec![
"npm install".to_string(),
"npm run build".to_string(),
"npm test".to_string(),
],
}),
});
let status = verify_trust(Path::new("/repo"), &config, true, None);
match status {
TrustStatus::NeedsConfirmation { commands, .. } => {
assert_eq!(commands.len(), 3);
assert_eq!(commands[0], "npm install");
assert_eq!(commands[1], "npm run build");
assert_eq!(commands[2], "npm test");
}
_ => panic!("Expected NeedsConfirmation, got {:?}", status),
}
}
#[test]
fn test_verify_trust_hooks_disabled() {
let mut config = Config::default();
config.hooks = Some(crate::config::HooksConfig {
post_create: Some(crate::config::HookConfig {
enabled: false,
commands: vec!["npm install".to_string()],
}),
});
let status = verify_trust(Path::new("/repo"), &config, true, None);
assert!(matches!(status, TrustStatus::NoHooks));
}
#[test]
fn test_verify_trust_empty_commands() {
let mut config = Config::default();
config.hooks = Some(crate::config::HooksConfig {
post_create: Some(crate::config::HookConfig {
enabled: true,
commands: vec![],
}),
});
let status = verify_trust(Path::new("/repo"), &config, true, None);
assert!(matches!(status, TrustStatus::NoHooks));
}
}