use agent_code_lib::config::{PermissionMode, PermissionRule, PermissionsConfig};
use agent_code_lib::permissions::{PermissionChecker, PermissionDecision};
fn json_cmd(cmd: &str) -> serde_json::Value {
serde_json::json!({"command": cmd})
}
fn json_file(path: &str) -> serde_json::Value {
serde_json::json!({"file_path": path})
}
fn checker_with_mode(mode: PermissionMode) -> PermissionChecker {
PermissionChecker::from_config(&PermissionsConfig {
default_mode: mode,
rules: Vec::new(),
})
}
#[test]
fn allow_mode_allows_all_tool_operations() {
let checker = checker_with_mode(PermissionMode::Allow);
assert!(matches!(
checker.check("Bash", &json_cmd("ls -la")),
PermissionDecision::Allow
));
assert!(matches!(
checker.check("FileWrite", &json_file("src/main.rs")),
PermissionDecision::Allow
));
assert!(matches!(
checker.check("FileRead", &json_file("README.md")),
PermissionDecision::Allow
));
}
#[test]
fn deny_mode_denies_all_operations() {
let checker = checker_with_mode(PermissionMode::Deny);
assert!(matches!(
checker.check("Bash", &json_cmd("echo hello")),
PermissionDecision::Deny(_)
));
assert!(matches!(
checker.check("FileWrite", &json_file("src/lib.rs")),
PermissionDecision::Deny(_)
));
assert!(matches!(
checker.check("FileRead", &json_file("Cargo.toml")),
PermissionDecision::Deny(_)
));
}
#[test]
fn ask_mode_returns_ask_decisions() {
let checker = checker_with_mode(PermissionMode::Ask);
assert!(matches!(
checker.check("Bash", &json_cmd("cargo build")),
PermissionDecision::Ask(_)
));
assert!(matches!(
checker.check("FileWrite", &json_file("src/lib.rs")),
PermissionDecision::Ask(_)
));
}
#[test]
fn plan_mode_denies_write_tools() {
let checker = checker_with_mode(PermissionMode::Plan);
assert!(matches!(
checker.check("FileWrite", &json_file("src/lib.rs")),
PermissionDecision::Deny(_)
));
assert!(matches!(
checker.check("FileEdit", &json_file("src/main.rs")),
PermissionDecision::Deny(_)
));
assert!(matches!(
checker.check("Bash", &json_cmd("rm -rf /")),
PermissionDecision::Deny(_)
));
}
#[test]
fn protected_directories_always_denied_for_writes() {
let checker = checker_with_mode(PermissionMode::Allow);
assert!(matches!(
checker.check("FileWrite", &json_file(".git/config")),
PermissionDecision::Deny(_)
));
assert!(matches!(
checker.check("FileEdit", &json_file("repo/.git/HEAD")),
PermissionDecision::Deny(_)
));
assert!(matches!(
checker.check("FileWrite", &json_file(".husky/pre-commit")),
PermissionDecision::Deny(_)
));
assert!(matches!(
checker.check("FileEdit", &json_file("node_modules/pkg/index.js")),
PermissionDecision::Deny(_)
));
assert!(matches!(
checker.check("NotebookEdit", &json_file("project/node_modules/a.ipynb")),
PermissionDecision::Deny(_)
));
}
#[test]
fn protected_directories_always_allowed_for_reads() {
let checker = checker_with_mode(PermissionMode::Ask);
assert!(matches!(
checker.check("FileRead", &json_file(".git/config")),
PermissionDecision::Ask(_)
));
let checker_allow = checker_with_mode(PermissionMode::Allow);
assert!(matches!(
checker_allow.check("FileRead", &json_file(".git/HEAD")),
PermissionDecision::Allow
));
assert!(matches!(
checker_allow.check("FileRead", &json_file("node_modules/pkg/lib.js")),
PermissionDecision::Allow
));
assert!(matches!(
checker_allow.check("FileRead", &json_file(".husky/pre-commit")),
PermissionDecision::Allow
));
let checker_deny = checker_with_mode(PermissionMode::Deny);
assert!(matches!(
checker_deny.check_read("FileRead", &json_file(".git/config")),
PermissionDecision::Allow
));
}
#[test]
fn specific_rule_overrides_default_mode() {
let checker = PermissionChecker::from_config(&PermissionsConfig {
default_mode: PermissionMode::Deny,
rules: vec![PermissionRule {
tool: "Bash".into(),
pattern: Some("git *".into()),
action: PermissionMode::Allow,
}],
});
assert!(matches!(
checker.check("Bash", &json_cmd("git status")),
PermissionDecision::Allow
));
assert!(matches!(
checker.check("Bash", &json_cmd("git push origin main")),
PermissionDecision::Allow
));
assert!(matches!(
checker.check("Bash", &json_cmd("ls -la")),
PermissionDecision::Deny(_)
));
assert!(matches!(
checker.check("FileWrite", &json_file("src/lib.rs")),
PermissionDecision::Deny(_)
));
}
#[test]
fn glob_pattern_matching_git_star() {
let checker = PermissionChecker::from_config(&PermissionsConfig {
default_mode: PermissionMode::Deny,
rules: vec![PermissionRule {
tool: "Bash".into(),
pattern: Some("git *".into()),
action: PermissionMode::Allow,
}],
});
assert!(matches!(
checker.check("Bash", &json_cmd("git log --oneline")),
PermissionDecision::Allow
));
assert!(matches!(
checker.check("Bash", &json_cmd("git")),
PermissionDecision::Deny(_)
));
}
#[test]
fn glob_pattern_matching_rs_files() {
let checker = PermissionChecker::from_config(&PermissionsConfig {
default_mode: PermissionMode::Deny,
rules: vec![PermissionRule {
tool: "FileWrite".into(),
pattern: Some("*.rs".into()),
action: PermissionMode::Allow,
}],
});
assert!(matches!(
checker.check("FileWrite", &json_file("main.rs")),
PermissionDecision::Allow
));
assert!(matches!(
checker.check("FileWrite", &json_file("main.py")),
PermissionDecision::Deny(_)
));
}
#[test]
fn first_matching_rule_wins() {
let checker = PermissionChecker::from_config(&PermissionsConfig {
default_mode: PermissionMode::Ask,
rules: vec![
PermissionRule {
tool: "Bash".into(),
pattern: Some("git *".into()),
action: PermissionMode::Allow,
},
PermissionRule {
tool: "Bash".into(),
pattern: None,
action: PermissionMode::Deny,
},
],
});
assert!(matches!(
checker.check("Bash", &json_cmd("git diff")),
PermissionDecision::Allow
));
assert!(matches!(
checker.check("Bash", &json_cmd("ls")),
PermissionDecision::Deny(_)
));
assert!(matches!(
checker.check("FileWrite", &json_file("src/lib.rs")),
PermissionDecision::Ask(_)
));
}
#[test]
fn mixed_rules_different_tools() {
let checker = PermissionChecker::from_config(&PermissionsConfig {
default_mode: PermissionMode::Ask,
rules: vec![
PermissionRule {
tool: "Bash".into(),
pattern: Some("git *".into()),
action: PermissionMode::Allow,
},
PermissionRule {
tool: "FileWrite".into(),
pattern: Some("*node_modules*".into()),
action: PermissionMode::Deny,
},
PermissionRule {
tool: "FileWrite".into(),
pattern: Some("*.rs".into()),
action: PermissionMode::Allow,
},
],
});
assert!(matches!(
checker.check("Bash", &json_cmd("git status")),
PermissionDecision::Allow
));
assert!(matches!(
checker.check("Bash", &json_cmd("cargo test")),
PermissionDecision::Ask(_)
));
assert!(matches!(
checker.check("FileWrite", &json_file("lib.rs")),
PermissionDecision::Allow
));
assert!(matches!(
checker.check("FileWrite", &json_file("script.py")),
PermissionDecision::Ask(_)
));
}
#[test]
fn wildcard_tool_rule_matches_all_tools() {
let checker = PermissionChecker::from_config(&PermissionsConfig {
default_mode: PermissionMode::Deny,
rules: vec![PermissionRule {
tool: "*".into(),
pattern: None,
action: PermissionMode::Allow,
}],
});
assert!(matches!(
checker.check("Bash", &json_cmd("ls")),
PermissionDecision::Allow
));
assert!(matches!(
checker.check("FileRead", &json_file("a.txt")),
PermissionDecision::Allow
));
assert!(matches!(
checker.check("FileWrite", &json_file(".git/config")),
PermissionDecision::Deny(_)
));
}
#[test]
fn accept_edits_mode_allows_operations() {
let checker = checker_with_mode(PermissionMode::AcceptEdits);
assert!(matches!(
checker.check("FileWrite", &json_file("src/lib.rs")),
PermissionDecision::Allow
));
assert!(matches!(
checker.check("FileEdit", &json_file("src/main.rs")),
PermissionDecision::Allow
));
assert!(matches!(
checker.check("Bash", &json_cmd("cargo build")),
PermissionDecision::Allow
));
}