use std::path::Path;
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) enum Administered {
Unset,
Set(String),
Unreadable(String),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub(crate) enum Hive {
Machine,
User,
}
impl Hive {
pub(crate) fn name(self) -> &'static str {
match self {
Hive::Machine => "HKLM",
Hive::User => "HKCU",
}
}
}
pub(crate) const READ_BY_REAL_HOSTS: bool = !cfg!(any(test, feature = "test-support"));
pub(crate) fn listed(dir: &Path) -> Vec<std::path::PathBuf> {
std::fs::read_dir(dir)
.into_iter()
.flatten()
.flatten()
.map(|entry| entry.path())
.collect()
}
pub(crate) fn read_text(path: &Path) -> Administered {
match std::fs::read(path) {
Ok(bytes) => match String::from_utf8(bytes) {
Ok(text) => Administered::Set(text),
Err(_) => Administered::Unreadable("is not UTF-8 text".into()),
},
Err(e) if e.kind() == std::io::ErrorKind::NotFound => Administered::Unset,
Err(e) => Administered::Unreadable(format!("cannot be read: {e}")),
}
}
#[cfg(test)]
#[allow(
clippy::disallowed_methods,
reason = "a test sets up its scratch files as a person or a tool would"
)]
mod tests {
use super::*;
fn scratch(name: &str) -> std::path::PathBuf {
let dir = std::env::temp_dir().join(format!(
"pitboard-administered-{name}-{}-{:?}",
std::process::id(),
std::thread::current().id()
));
let _ = std::fs::remove_dir_all(&dir);
std::fs::create_dir_all(&dir).expect("a scratch folder");
dir
}
#[test]
fn a_file_is_unset_set_or_unreadable() {
let dir = scratch("file");
assert_eq!(read_text(&dir.join("absent.toml")), Administered::Unset);
std::fs::write(dir.join("set.toml"), "a = 1\n").expect("written");
assert_eq!(
read_text(&dir.join("set.toml")),
Administered::Set("a = 1\n".into())
);
std::fs::write(dir.join("bytes.toml"), [0xff, 0xfe, 0x00]).expect("written");
assert_eq!(
read_text(&dir.join("bytes.toml")),
Administered::Unreadable("is not UTF-8 text".into())
);
let folder = read_text(&dir);
assert!(
matches!(&folder, Administered::Unreadable(why) if why.starts_with("cannot be read")),
"a folder where the file should be cannot be read as one: {folder:?}"
);
let _ = std::fs::remove_dir_all(&dir);
}
#[test]
fn a_build_for_tests_reads_nothing_an_administrator_set() {
const { assert!(!READ_BY_REAL_HOSTS) };
let ctx = crate::context::Context::for_unit_test();
assert_eq!(
ctx.host()
.administered_file(Path::new("/etc/codex/requirements.toml")),
Administered::Unset
);
assert_eq!(
ctx.host()
.managed_preference("com.openai.codex", "requirements_toml_base64"),
Administered::Unset
);
assert_eq!(
ctx.host()
.administered_folder(Path::new("/etc/claude-code/managed-settings.d")),
Vec::<std::path::PathBuf>::new()
);
for hive in [Hive::Machine, Hive::User] {
assert_eq!(
ctx.host()
.policy(hive, r"SOFTWARE\Policies\ClaudeCode", "Settings"),
Administered::Unset
);
}
}
}