use super::{
add_to_allowlist, check_path, remove_from_allowlist, AllowlistCheck, AllowlistConfig,
AllowlistEntry,
};
use std::fs;
use std::path::{Path, PathBuf};
fn tmp_dir(label: &str) -> PathBuf {
let pid = std::process::id();
let nanos = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_nanos();
let p = std::env::temp_dir().join(format!("ts-allowlist-{label}-{pid}-{nanos}"));
let _ = fs::remove_dir_all(&p);
fs::create_dir_all(&p).unwrap();
p
}
fn allowlist_file(dir: &Path) -> PathBuf {
dir.join("allowlist.toml")
}
fn entry(path: &Path) -> AllowlistEntry {
AllowlistEntry {
path: path.to_path_buf(),
name: None,
exclude: vec![],
extensions: vec![],
skip_kg: false,
}
}
#[test]
fn denylist_blocks_ssh_dir() {
let path = PathBuf::from(format!("{}/.ssh", dirs::home_dir().unwrap().display()));
assert!(
super::is_denied(&path).is_some(),
"expected denial for {path:?}"
);
}
#[test]
fn denylist_blocks_aws_dir() {
let path = PathBuf::from(format!("{}/.aws", dirs::home_dir().unwrap().display()));
assert!(super::is_denied(&path).is_some());
}
#[test]
fn denylist_blocks_tmp() {
let path = PathBuf::from("/tmp/my-project");
assert!(super::is_denied(&path).is_some());
}
#[test]
fn denylist_blocks_env_file_in_path() {
let path = PathBuf::from("/projects/.env/configs");
assert!(super::is_denied(&path).is_some());
}
#[test]
fn denylist_blocks_home_toplevel() {
let home = dirs::home_dir().unwrap();
assert!(
super::is_denied(&home).is_some(),
"HOME itself must be denied: {home:?}"
);
let desktop = home.join("Desktop");
assert!(
super::is_denied(&desktop).is_some(),
"~/Desktop must be denied: {desktop:?}"
);
let downloads = home.join("Downloads");
assert!(super::is_denied(&downloads).is_some());
}
#[test]
fn denylist_allows_safe_path() {
let path = PathBuf::from("/srv/my-safe-project");
assert!(
super::is_denied(&path).is_none(),
"expected safe path to pass: {path:?}"
);
}
#[test]
fn denylist_allows_projects_under_home() {
let home = dirs::home_dir().unwrap();
let projects = home.join("Projects").join("my-repo");
assert!(
super::is_denied(&projects).is_none(),
"~/Projects/my-repo must pass the denylist: {projects:?}"
);
}
#[test]
fn load_returns_empty_when_missing() {
let dir = tmp_dir("missing");
let path = allowlist_file(&dir);
let cfg = AllowlistConfig::load_from(&path).unwrap();
assert!(cfg.entries.is_empty());
let _ = fs::remove_dir_all(&dir);
}
#[test]
fn load_errors_on_malformed_toml() {
let dir = tmp_dir("malformed");
let path = allowlist_file(&dir);
fs::write(&path, "not: : : valid ===").unwrap();
assert!(AllowlistConfig::load_from(&path).is_err());
let _ = fs::remove_dir_all(&dir);
}
#[test]
fn roundtrip_preserves_all_fields() {
let dir = tmp_dir("roundtrip");
let path = allowlist_file(&dir);
let cfg = AllowlistConfig {
entries: vec![AllowlistEntry {
path: PathBuf::from("/srv/my-project"),
name: Some("my-proj".into()),
exclude: vec!["target/".into()],
extensions: vec!["rs".into(), "toml".into()],
skip_kg: true,
}],
};
cfg.save_to(&path).unwrap();
let loaded = AllowlistConfig::load_from(&path).unwrap();
assert_eq!(cfg, loaded);
let _ = fs::remove_dir_all(&dir);
}
#[test]
fn save_creates_parent_dirs() {
let dir = tmp_dir("parent");
let path = dir.join("nested").join("deep").join("indexes.toml");
AllowlistConfig::default().save_to(&path).unwrap();
assert!(path.exists());
let _ = fs::remove_dir_all(&dir);
}
#[test]
fn upsert_replaces_existing_by_path() {
let dir = tmp_dir("upsert-replace");
fs::create_dir_all(dir.join("proj")).unwrap();
let project = dir.join("proj");
let mut cfg = AllowlistConfig::default();
cfg.upsert(AllowlistEntry {
path: project.clone(),
name: Some("old".into()),
exclude: vec![],
extensions: vec![],
skip_kg: false,
});
cfg.upsert(AllowlistEntry {
path: project.clone(),
name: Some("new".into()),
exclude: vec!["*.log".into()],
extensions: vec!["rs".into()],
skip_kg: true,
});
assert_eq!(cfg.entries.len(), 1);
assert_eq!(cfg.entries[0].name, Some("new".into()));
assert!(cfg.entries[0].skip_kg);
let _ = fs::remove_dir_all(&dir);
}
#[test]
fn upsert_appends_new_path() {
let mut cfg = AllowlistConfig::default();
cfg.upsert(entry(Path::new("/srv/a")));
cfg.upsert(entry(Path::new("/srv/b")));
assert_eq!(cfg.entries.len(), 2);
}
#[test]
fn remove_by_path() {
let mut cfg = AllowlistConfig::default();
cfg.upsert(entry(Path::new("/srv/a")));
cfg.upsert(entry(Path::new("/srv/b")));
let removed = cfg.remove(Path::new("/srv/a"));
assert!(removed.is_some());
assert_eq!(cfg.entries.len(), 1);
assert_eq!(cfg.entries[0].path, PathBuf::from("/srv/b"));
}
#[test]
fn remove_returns_none_for_unknown() {
let mut cfg = AllowlistConfig::default();
cfg.upsert(entry(Path::new("/srv/a")));
assert!(cfg.remove(Path::new("/srv/unknown")).is_none());
assert_eq!(cfg.entries.len(), 1);
}
#[test]
fn allowlist_contains_known_path() {
let mut cfg = AllowlistConfig::default();
cfg.upsert(entry(Path::new("/srv/my-project")));
assert!(cfg.contains(Path::new("/srv/my-project")));
}
#[test]
fn allowlist_misses_unknown_path() {
let cfg = AllowlistConfig::default();
assert!(!cfg.contains(Path::new("/srv/unknown")));
}
#[test]
fn check_path_denied_by_denylist() {
let dir = tmp_dir("cp-denied");
let allowlist = allowlist_file(&dir);
let ssh = PathBuf::from(format!("{}/.ssh", dirs::home_dir().unwrap().display()));
let mut cfg = AllowlistConfig::default();
cfg.upsert(entry(&ssh));
cfg.save_to(&allowlist).unwrap();
let result = check_path(&ssh, Some(&allowlist)).unwrap();
assert!(
matches!(result, AllowlistCheck::Denied { .. }),
"expected Denied, got {result:?}"
);
let _ = fs::remove_dir_all(&dir);
}
#[test]
fn check_path_not_allowlisted() {
let dir = tmp_dir("cp-not-allowlisted");
let allowlist = allowlist_file(&dir);
AllowlistConfig::default().save_to(&allowlist).unwrap();
let safe_path = PathBuf::from("/srv/my-safe-project");
let result = check_path(&safe_path, Some(&allowlist)).unwrap();
assert_eq!(result, AllowlistCheck::NotAllowlisted);
let _ = fs::remove_dir_all(&dir);
}
#[test]
fn check_path_not_allowlisted_when_file_missing() {
let dir = tmp_dir("cp-no-file");
let allowlist = allowlist_file(&dir);
let safe_path = PathBuf::from("/srv/my-safe-project");
let result = check_path(&safe_path, Some(&allowlist)).unwrap();
assert_eq!(result, AllowlistCheck::NotAllowlisted);
let _ = fs::remove_dir_all(&dir);
}
#[test]
fn check_path_allowed() {
let dir = tmp_dir("cp-allowed");
let allowlist = allowlist_file(&dir);
let safe_path = PathBuf::from("/srv/my-safe-project");
let mut cfg = AllowlistConfig::default();
cfg.upsert(entry(&safe_path));
cfg.save_to(&allowlist).unwrap();
let result = check_path(&safe_path, Some(&allowlist)).unwrap();
assert_eq!(result, AllowlistCheck::Allowed);
let _ = fs::remove_dir_all(&dir);
}
#[test]
fn denylist_takes_priority_over_allowlist() {
let dir = tmp_dir("deny-over-allowlist");
let allowlist = allowlist_file(&dir);
let sensitive = PathBuf::from(format!("{}/.aws", dirs::home_dir().unwrap().display()));
let mut cfg = AllowlistConfig::default();
cfg.upsert(entry(&sensitive));
cfg.save_to(&allowlist).unwrap();
let result = check_path(&sensitive, Some(&allowlist)).unwrap();
assert!(
matches!(result, AllowlistCheck::Denied { .. }),
"denylist must override allowlist"
);
let _ = fs::remove_dir_all(&dir);
}
#[test]
fn add_to_allowlist_persists_entry() {
let dir = tmp_dir("add-persists");
let allowlist = allowlist_file(&dir);
let safe = PathBuf::from("/srv/my-project");
add_to_allowlist(entry(&safe), Some(&allowlist)).unwrap();
let result = check_path(&safe, Some(&allowlist)).unwrap();
assert_eq!(result, AllowlistCheck::Allowed);
let _ = fs::remove_dir_all(&dir);
}
#[test]
fn add_to_allowlist_blocked_by_denylist() {
let dir = tmp_dir("add-blocked");
let allowlist = allowlist_file(&dir);
let ssh = PathBuf::from(format!("{}/.ssh", dirs::home_dir().unwrap().display()));
let err = add_to_allowlist(entry(&ssh), Some(&allowlist));
assert!(err.is_err());
let cfg = AllowlistConfig::load_from(&allowlist).unwrap();
assert!(cfg.entries.is_empty());
let _ = fs::remove_dir_all(&dir);
}
#[test]
fn remove_from_allowlist_removes_entry() {
let dir = tmp_dir("remove-entry");
let allowlist = allowlist_file(&dir);
let safe = PathBuf::from("/srv/my-project");
add_to_allowlist(entry(&safe), Some(&allowlist)).unwrap();
assert_eq!(
check_path(&safe, Some(&allowlist)).unwrap(),
AllowlistCheck::Allowed
);
remove_from_allowlist(&safe, Some(&allowlist)).unwrap();
assert_eq!(
check_path(&safe, Some(&allowlist)).unwrap(),
AllowlistCheck::NotAllowlisted
);
let _ = fs::remove_dir_all(&dir);
}
#[test]
fn remove_from_allowlist_noop_when_absent() {
let dir = tmp_dir("remove-noop");
let allowlist = allowlist_file(&dir);
AllowlistConfig::default().save_to(&allowlist).unwrap();
remove_from_allowlist(Path::new("/srv/nonexistent"), Some(&allowlist)).unwrap();
let _ = fs::remove_dir_all(&dir);
}
#[test]
fn allowlist_path_ends_with_expected_suffix() {
let p = AllowlistConfig::default_path();
let s = p.to_string_lossy();
assert!(
s.ends_with("trusty-search/allowlist.toml") || s.ends_with("trusty-search-allowlist.toml"),
"must be allowlist.toml, not indexes.toml: {s}"
);
}
#[test]
fn denylist_allows_secrets_manager_project() {
let path = PathBuf::from("/home/user/Projects/secrets-manager");
assert!(
super::is_denied(&path).is_none(),
"secrets-manager project must not be denied by denylist: {path:?}"
);
}
#[test]
fn denylist_allows_credentials_validator_project() {
let path = PathBuf::from("/srv/app/credentials-validator");
assert!(
super::is_denied(&path).is_none(),
"credentials-validator project must not be denied: {path:?}"
);
}
#[test]
fn denylist_allows_config_service_project() {
let path = PathBuf::from("/data/projects/config-service");
assert!(
super::is_denied(&path).is_none(),
"config-service project must not be denied: {path:?}"
);
}
#[test]
fn denylist_blocks_exact_secrets_component() {
let path = PathBuf::from("/etc/secrets/x");
assert!(
super::is_denied(&path).is_some(),
"/etc/secrets must be denied: {path:?}"
);
}
#[test]
fn denylist_blocks_dot_config_component() {
let home = dirs::home_dir().unwrap();
let path = home.join(".config").join("trusty");
assert!(
super::is_denied(&path).is_some(),
"~/.config/trusty must be denied: {path:?}"
);
}
#[test]
fn denylist_blocks_env_file() {
let path = PathBuf::from("/home/user/myproject/.env");
assert!(
super::is_denied(&path).is_some(),
".env file must be denied: {path:?}"
);
}
#[test]
fn denylist_denied_path_still_rejected_when_allowlisted() {
let dir = tmp_dir("denylist-priority");
let allowlist = allowlist_file(&dir);
let sensitive = PathBuf::from("/etc/secrets");
let mut cfg = AllowlistConfig::default();
cfg.upsert(entry(&sensitive));
cfg.save_to(&allowlist).unwrap();
let result = check_path(&sensitive, Some(&allowlist)).unwrap();
assert!(
matches!(result, AllowlistCheck::Denied { .. }),
"denylist must block an allowlisted sensitive path; got {result:?}"
);
let _ = std::fs::remove_dir_all(&dir);
}