#![allow(clippy::unwrap_used)]
mod common;
use common::{run_rippy, run_rippy_in_dir, run_rippy_in_dir_with_args};
#[test]
fn config_deny_overrides() {
let config_path = format!("{}/tests/fixtures/test.rippy", env!("CARGO_MANIFEST_DIR"));
let json = r#"{"tool_name":"Bash","tool_input":{"command":"rm -rf /tmp/stuff"}}"#;
let (stdout, code) = run_rippy(json, "claude", &["--config", &config_path]);
assert_eq!(code, 2);
let v: serde_json::Value = serde_json::from_str(&stdout).unwrap();
assert_eq!(v["hookSpecificOutput"]["permissionDecision"], "deny");
assert!(
v["hookSpecificOutput"]["permissionDecisionReason"]
.as_str()
.unwrap()
.contains("use trash instead")
);
}
#[test]
fn config_allow_overrides() {
let config_path = format!("{}/tests/fixtures/test.rippy", env!("CARGO_MANIFEST_DIR"));
let json = r#"{"tool_name":"Bash","tool_input":{"command":"git status"}}"#;
let (stdout, code) = run_rippy(json, "claude", &["--config", &config_path]);
assert_eq!(code, 0);
let v: serde_json::Value = serde_json::from_str(&stdout).unwrap();
assert_eq!(v["hookSpecificOutput"]["permissionDecision"], "allow");
}
fn recommended_config_path() -> String {
format!("{}/examples/recommended.rippy", env!("CARGO_MANIFEST_DIR"))
}
#[test]
fn recommended_config_allows_defaults_read() {
let json = r#"{"tool_name":"Bash","tool_input":{"command":"defaults read com.apple.finder"}}"#;
let (stdout, code) = run_rippy(json, "claude", &["--config", &recommended_config_path()]);
assert_eq!(code, 0);
let v: serde_json::Value = serde_json::from_str(&stdout).unwrap();
assert_eq!(v["hookSpecificOutput"]["permissionDecision"], "allow");
}
#[test]
fn recommended_config_asks_defaults_write() {
let json = r#"{"tool_name":"Bash","tool_input":{"command":"defaults write com.apple.finder key val"}}"#;
let (stdout, code) = run_rippy(json, "claude", &["--config", &recommended_config_path()]);
assert_eq!(code, 2);
let v: serde_json::Value = serde_json::from_str(&stdout).unwrap();
assert_eq!(v["hookSpecificOutput"]["permissionDecision"], "ask");
}
#[test]
fn recommended_config_asks_kill() {
let json = r#"{"tool_name":"Bash","tool_input":{"command":"kill -9 1234"}}"#;
let (stdout, code) = run_rippy(json, "claude", &["--config", &recommended_config_path()]);
assert_eq!(code, 2);
let v: serde_json::Value = serde_json::from_str(&stdout).unwrap();
assert_eq!(v["hookSpecificOutput"]["permissionDecision"], "ask");
}
#[test]
fn recommended_config_asks_dd() {
let json =
r#"{"tool_name":"Bash","tool_input":{"command":"dd if=/dev/zero of=/dev/sda bs=1M"}}"#;
let (stdout, code) = run_rippy(json, "claude", &["--config", &recommended_config_path()]);
assert_eq!(code, 2);
let v: serde_json::Value = serde_json::from_str(&stdout).unwrap();
assert_eq!(v["hookSpecificOutput"]["permissionDecision"], "ask");
}
#[test]
fn recommended_config_allows_xattr_bare() {
let json = r#"{"tool_name":"Bash","tool_input":{"command":"xattr"}}"#;
let (stdout, code) = run_rippy(json, "claude", &["--config", &recommended_config_path()]);
assert_eq!(code, 0);
let v: serde_json::Value = serde_json::from_str(&stdout).unwrap();
assert_eq!(v["hookSpecificOutput"]["permissionDecision"], "allow");
}
#[test]
fn recommended_config_asks_xattr_write() {
let json = r#"{"tool_name":"Bash","tool_input":{"command":"xattr -w attr val file.txt"}}"#;
let (stdout, code) = run_rippy(json, "claude", &["--config", &recommended_config_path()]);
assert_eq!(code, 2);
let v: serde_json::Value = serde_json::from_str(&stdout).unwrap();
assert_eq!(v["hookSpecificOutput"]["permissionDecision"], "ask");
}
#[test]
fn recommended_config_allows_ansible_doc() {
let json = r#"{"tool_name":"Bash","tool_input":{"command":"ansible-doc copy"}}"#;
let (stdout, code) = run_rippy(json, "claude", &["--config", &recommended_config_path()]);
assert_eq!(code, 0);
let v: serde_json::Value = serde_json::from_str(&stdout).unwrap();
assert_eq!(v["hookSpecificOutput"]["permissionDecision"], "allow");
}
#[test]
fn recommended_config_allows_diskutil_list() {
let json = r#"{"tool_name":"Bash","tool_input":{"command":"diskutil list"}}"#;
let (stdout, code) = run_rippy(json, "claude", &["--config", &recommended_config_path()]);
assert_eq!(code, 0);
let v: serde_json::Value = serde_json::from_str(&stdout).unwrap();
assert_eq!(v["hookSpecificOutput"]["permissionDecision"], "allow");
}
#[test]
fn recommended_config_exact_match_dmesg_allows_bare() {
let json = r#"{"tool_name":"Bash","tool_input":{"command":"dmesg"}}"#;
let (stdout, code) = run_rippy(json, "claude", &["--config", &recommended_config_path()]);
assert_eq!(code, 0);
let v: serde_json::Value = serde_json::from_str(&stdout).unwrap();
assert_eq!(v["hookSpecificOutput"]["permissionDecision"], "allow");
}
#[test]
fn recommended_config_exact_match_dmesg_asks_clear() {
let json = r#"{"tool_name":"Bash","tool_input":{"command":"dmesg -c"}}"#;
let (stdout, code) = run_rippy(json, "claude", &["--config", &recommended_config_path()]);
assert_eq!(code, 2);
let v: serde_json::Value = serde_json::from_str(&stdout).unwrap();
assert_eq!(v["hookSpecificOutput"]["permissionDecision"], "ask");
}
#[test]
fn conditional_rule_file_exists_skipped_when_missing() {
let dir = tempfile::TempDir::new().unwrap();
std::fs::write(
dir.path().join(".rippy.toml"),
r#"
[[rules]]
action = "deny"
pattern = "echo *"
message = "blocked"
[rules.when]
file-exists = "Cargo.toml"
"#,
)
.unwrap();
let json = r#"{"tool_name":"Bash","tool_input":{"command":"echo hello"}}"#;
let (_stdout, code) = run_rippy_in_dir(json, "claude", dir.path());
assert_eq!(code, 0);
}
#[test]
fn conditional_rule_file_exists_applies_when_present() {
let dir = tempfile::TempDir::new().unwrap();
std::fs::write(dir.path().join("Cargo.toml"), "").unwrap();
std::fs::write(
dir.path().join(".rippy.toml"),
r#"
[[rules]]
action = "deny"
pattern = "echo *"
message = "blocked"
[rules.when]
file-exists = "Cargo.toml"
"#,
)
.unwrap();
let json = r#"{"tool_name":"Bash","tool_input":{"command":"echo hello"}}"#;
let config = dir.path().join(".rippy.toml");
let config_str = config.to_str().unwrap();
let (stdout, code) =
run_rippy_in_dir_with_args(json, "claude", dir.path(), &["--config", config_str]);
assert_eq!(code, 2);
let v: serde_json::Value = serde_json::from_str(&stdout).unwrap();
assert_eq!(v["hookSpecificOutput"]["permissionDecision"], "deny");
}
#[test]
fn conditional_rule_branch_not_main() {
let dir = tempfile::TempDir::new().unwrap();
std::fs::write(
dir.path().join(".rippy.toml"),
r#"
[[rules]]
action = "deny"
pattern = "echo *"
message = "only blocked on main"
[rules.when]
branch = { eq = "main" }
"#,
)
.unwrap();
let json = r#"{"tool_name":"Bash","tool_input":{"command":"echo hello"}}"#;
let (_stdout, code) = run_rippy_in_dir(json, "claude", dir.path());
assert_eq!(code, 0);
}