use std::path::{Path, PathBuf};
use std::process::Command;
struct Fixture {
dir: PathBuf,
}
impl Fixture {
fn new(name: &str) -> Fixture {
let dir =
std::env::temp_dir().join(format!("amont-agent-scope-{}-{name}", std::process::id()));
let _ = std::fs::remove_dir_all(&dir);
std::fs::create_dir_all(dir.join("repo")).expect("scratch dir");
std::fs::write(dir.join("global"), "").expect("global config");
std::fs::write(dir.join("system"), "").expect("system config");
let f = Fixture { dir };
f.git(&["init", "-q", "--template=", "."]);
f
}
fn repo(&self) -> PathBuf {
self.dir.join("repo")
}
fn git(&self, args: &[&str]) {
let out = Command::new("git")
.args(args)
.current_dir(self.repo())
.env("GIT_CONFIG_GLOBAL", self.dir.join("global"))
.env("GIT_CONFIG_SYSTEM", self.dir.join("system"))
.output()
.expect("git runs");
assert!(
out.status.success(),
"fixture: git {args:?} failed: {}",
String::from_utf8_lossy(&out.stderr)
);
}
fn set(&self, scope: &str, key: &str, value: &str) {
self.git(&["config", scope, key, value]);
}
fn stance_of(&self, rule: &str) -> String {
let out = Command::new(env!("CARGO_BIN_EXE_amont-agent"))
.arg("status")
.current_dir(self.repo())
.env("CLAUDE_CONFIG_DIR", self.dir.join("claude"))
.env("GIT_CONFIG_GLOBAL", self.dir.join("global"))
.env("GIT_CONFIG_SYSTEM", self.dir.join("system"))
.env_remove("AMONT_AGENT_OFF")
.output()
.expect("the binary runs");
assert!(
out.status.success(),
"status exited {:?}",
out.status.code()
);
let text = String::from_utf8_lossy(&out.stdout).into_owned();
let line = text
.lines()
.find(|l| l.starts_with(rule))
.unwrap_or_else(|| panic!("no line for {rule} in:\n{text}"));
line.split_whitespace()
.nth(2)
.unwrap_or_default()
.to_string()
}
}
impl Drop for Fixture {
fn drop(&mut self) {
let _ = std::fs::remove_dir_all(&self.dir);
}
}
#[test]
fn a_repository_cannot_lower_a_stance() {
let f = Fixture::new("local-demote");
f.set("--local", "amont.agent.pipe-to-tail.stance", "observe");
assert_eq!(
f.stance_of("pipe-to-tail"),
"deny",
"a --local key moved the stance the binary enforces"
);
}
#[test]
fn a_repository_cannot_raise_a_stance_either() {
let f = Fixture::new("local-promote");
f.set("--local", "amont.agent.git-add-broad.stance", "deny");
assert_eq!(f.stance_of("git-add-broad"), "observe");
}
#[test]
fn a_repository_cannot_disable_the_guard() {
let f = Fixture::new("local-enabled");
f.set("--local", "amont.agent.enabled", "false");
assert_eq!(f.stance_of("pipe-to-tail"), "deny");
}
#[test]
fn the_machines_own_config_still_decides() {
let f = Fixture::new("global");
f.set("--global", "amont.agent.pipe-to-tail.stance", "observe");
assert_eq!(f.stance_of("pipe-to-tail"), "observe");
let f = Fixture::new("global-off");
f.set("--global", "amont.agent.enabled", "false");
assert_eq!(f.stance_of("pipe-to-tail"), "observe");
}
#[test]
fn system_is_the_floor_and_global_outranks_it() {
let f = Fixture::new("system");
f.set("--system", "amont.agent.git-add-broad.stance", "advise");
assert_eq!(f.stance_of("git-add-broad"), "advise");
f.set("--global", "amont.agent.git-add-broad.stance", "observe");
assert_eq!(f.stance_of("git-add-broad"), "observe");
}
#[test]
fn the_blanket_floor_is_the_machines_too() {
let f = Fixture::new("floor");
f.set("--local", "amont.agent.stance", "deny");
assert_eq!(f.stance_of("git-add-broad"), "observe");
f.set("--global", "amont.agent.stance", "advise");
assert_eq!(f.stance_of("git-add-broad"), "advise");
}
#[test]
fn a_file_the_global_config_includes_is_read() {
let f = Fixture::new("include");
let extra = f.dir.join("extra");
let extra = extra.display().to_string();
f.git(&[
"config",
"--file",
&extra,
"amont.agent.pipe-to-tail.stance",
"observe",
]);
f.set("--global", "include.path", &extra);
assert_eq!(f.stance_of("pipe-to-tail"), "observe");
}
#[test]
fn a_file_the_global_config_includes_for_this_repository_is_read() {
let f = Fixture::new("include-if");
let extra = f.dir.join("extra");
let extra = extra.display().to_string();
f.git(&[
"config",
"--file",
&extra,
"amont.agent.pipe-to-tail.stance",
"observe",
]);
let condition = format!(
"includeIf.gitdir:amont-agent-scope-{}-include-if/repo/.path",
std::process::id()
);
f.set("--global", &condition, &extra);
assert_eq!(f.stance_of("pipe-to-tail"), "observe");
let f = Fixture::new("include-if-elsewhere");
let extra = f.dir.join("extra").display().to_string();
f.git(&[
"config",
"--file",
&extra,
"amont.agent.pipe-to-tail.stance",
"observe",
]);
f.set("--global", "includeIf.gitdir:/nowhere/.path", &extra);
assert_eq!(f.stance_of("pipe-to-tail"), "deny");
}
#[test]
fn a_relocated_global_config_is_still_read() {
let f = Fixture::new("relocated");
f.set("--global", "amont.agent.no-verify.stance", "deny");
assert_eq!(f.stance_of("no-verify"), "deny");
assert!(
Path::new(&f.dir.join("global")).exists(),
"the fixture's global config is the one that answered"
);
}