//! Tests for `.botsecrets [policy]` section integration with `Policy`, //! including `fermata.toml` alias support. use dirigent_fermata::core::{Decision, Op, Policy}; use std::fs; use tempfile::TempDir; /// Helper: create a temp project dir with optional `.botignore` and `.botsecrets` files. fn make_project(botignore: &str, botsecrets: &str) -> TempDir { let tmp = TempDir::new().unwrap(); if !botignore.is_empty() { fs::write(tmp.path().join(".botignore"), botignore).unwrap(); } if !botsecrets.is_empty() { fs::write(tmp.path().join(".botsecrets"), botsecrets).unwrap(); } tmp } #[test] fn botsecrets_policy_write_works() { let tmp = make_project( "", r#" [policy.write] patterns = ["vendor/**"] "#, ); let policy = Policy::load(tmp.path()).unwrap(); let target = tmp.path().join("vendor/lib.rs"); fs::create_dir_all(target.parent().unwrap()).unwrap(); fs::write(&target, "").unwrap(); // Write to vendor/** should be denied via .botsecrets [policy.write] assert!(matches!( policy.check(Op::Write, &target).unwrap(), Decision::Deny(_) )); // Read should still be allowed (no read rules) assert_eq!(policy.check(Op::Read, &target).unwrap(), Decision::Allow); } #[test] fn botsecrets_policy_read_works() { let tmp = make_project( "", r#" [policy.read] patterns = ["secrets/**"] "#, ); let policy = Policy::load(tmp.path()).unwrap(); let target = tmp.path().join("secrets/key.pem"); fs::create_dir_all(target.parent().unwrap()).unwrap(); fs::write(&target, "").unwrap(); assert!(matches!( policy.check(Op::Read, &target).unwrap(), Decision::Deny(_) )); assert_eq!(policy.check(Op::Write, &target).unwrap(), Decision::Allow); } #[test] fn botsecrets_without_policy_section_does_not_affect_policy() { let tmp = make_project( "", r#" [keys] include = ["MY_CUSTOM_KEY"] "#, ); let policy = Policy::load(tmp.path()).unwrap(); // Everything should be allowed — no policy rules let target = tmp.path().join("vendor/lib.rs"); fs::create_dir_all(target.parent().unwrap()).unwrap(); fs::write(&target, "").unwrap(); assert_eq!(policy.check(Op::Read, &target).unwrap(), Decision::Allow); assert_eq!(policy.check(Op::Write, &target).unwrap(), Decision::Allow); } #[test] fn botsecrets_policy_bash_deny_works() { let tmp = make_project( "", r#" [policy.bash] deny = ["rm -rf /"] ask = ["git push"] "#, ); let policy = Policy::load(tmp.path()).unwrap(); let d = policy.check_command("rm -rf /").unwrap(); assert!(matches!(d, Decision::Deny(_))); let d = policy.check_command("git push origin main").unwrap(); assert!(matches!(d, Decision::Ask(_))); let d = policy.check_command("cargo build").unwrap(); assert_eq!(d, Decision::Allow); } #[test] fn malformed_botsecrets_falls_back_to_defaults() { // .botsecrets is malformed — should still load with empty policy (fail-open) let tmp = TempDir::new().unwrap(); fs::write(tmp.path().join(".botsecrets"), "this is not valid toml [[[").unwrap(); let policy = Policy::load(tmp.path()).unwrap(); let target = tmp.path().join("anything.rs"); fs::write(&target, "").unwrap(); assert_eq!(policy.check(Op::Read, &target).unwrap(), Decision::Allow); } #[test] fn fermata_toml_works_as_alias() { // No .botsecrets, but fermata.toml exists — should be loaded let tmp = TempDir::new().unwrap(); fs::write( tmp.path().join("fermata.toml"), r#" [policy.write] patterns = ["generated/**"] "#, ) .unwrap(); let policy = Policy::load(tmp.path()).unwrap(); let target = tmp.path().join("generated/output.rs"); fs::create_dir_all(target.parent().unwrap()).unwrap(); fs::write(&target, "").unwrap(); assert!(matches!( policy.check(Op::Write, &target).unwrap(), Decision::Deny(_) )); } #[test] fn botsecrets_takes_priority_over_fermata_toml() { // Both .botsecrets and fermata.toml exist. // .botsecrets blocks writes to vendor/**, fermata.toml blocks writes to src/** // Only .botsecrets rules should apply. let tmp = TempDir::new().unwrap(); fs::write( tmp.path().join(".botsecrets"), r#" [policy.write] patterns = ["vendor/**"] "#, ) .unwrap(); fs::write( tmp.path().join("fermata.toml"), r#" [policy.write] patterns = ["src/**"] "#, ) .unwrap(); let policy = Policy::load(tmp.path()).unwrap(); // vendor/** should be blocked (from .botsecrets) let vendor_target = tmp.path().join("vendor/lib.rs"); fs::create_dir_all(vendor_target.parent().unwrap()).unwrap(); fs::write(&vendor_target, "").unwrap(); assert!(matches!( policy.check(Op::Write, &vendor_target).unwrap(), Decision::Deny(_) )); // src/** should NOT be blocked (fermata.toml was ignored because .botsecrets exists) let src_target = tmp.path().join("src/main.rs"); fs::create_dir_all(src_target.parent().unwrap()).unwrap(); fs::write(&src_target, "").unwrap(); assert_eq!( policy.check(Op::Write, &src_target).unwrap(), Decision::Allow ); }