use std::collections::BTreeMap;
use std::path::Path;
use super::{ChildEnvironment, EnvironmentDeclaration, EnvironmentError};
type TestResult = Result<(), String>;
fn context(entries: &[(&str, &str)]) -> BTreeMap<String, String> {
entries
.iter()
.map(|(name, value)| ((*name).to_owned(), (*value).to_owned()))
.collect()
}
fn declaration(names: &[&str]) -> Result<EnvironmentDeclaration, String> {
EnvironmentDeclaration::new(names.iter().copied())
.map_err(|error| format!("declaration {names:?} should be valid: {error}"))
}
#[test]
fn a_variable_the_declaration_does_not_name_is_absent_in_the_child() -> TestResult {
let parent = context(&[("PATH", "/usr/bin"), ("CLAUDECODE", "1"), ("HOME", "/u/t")]);
let resolved = declaration(&["PATH", "HOME"])?.resolve(&parent);
assert!(resolved.carries("PATH"), "PATH was declared");
assert!(resolved.carries("HOME"), "HOME was declared");
assert!(
!resolved.carries("CLAUDECODE"),
"CLAUDECODE was NOT declared, so no parent can put it in the child: {:?}",
resolved.pairs()
);
assert_eq!(resolved.pairs().len(), 2, "the child's WHOLE environment");
Ok(())
}
#[test]
fn the_resolved_pairs_are_the_childs_entire_environment_not_an_overlay() -> TestResult {
let parent = context(&[("A", "1"), ("B", "2"), ("C", "3")]);
let resolved = declaration(&["B"])?.resolve(&parent);
assert_eq!(resolved.pairs(), [("B".to_owned(), "2".to_owned())]);
Ok(())
}
#[test]
fn a_declared_name_the_context_does_not_carry_is_reported_absent_not_invented() -> TestResult {
let resolved =
declaration(&["PATH", "ANTHROPIC_API_KEY"])?.resolve(&context(&[("PATH", "/usr/bin")]));
assert_eq!(resolved.absent(), ["ANTHROPIC_API_KEY".to_owned()]);
assert!(
!resolved.carries("ANTHROPIC_API_KEY"),
"an unset variable must not become an empty-string variable"
);
Ok(())
}
#[test]
fn declaration_order_is_the_operators_and_duplicates_collapse_once() -> TestResult {
let parent = context(&[("A", "1"), ("B", "2")]);
let resolved = declaration(&["B", "A", "B"])?.resolve(&parent);
assert_eq!(
resolved.pairs(),
[
("B".to_owned(), "2".to_owned()),
("A".to_owned(), "1".to_owned())
]
);
assert_eq!(resolved.declared(), ["B", "A", "B"], "written as authored");
Ok(())
}
#[test]
fn a_blank_entry_is_refused_because_no_environment_can_carry_one() {
assert_eq!(
EnvironmentDeclaration::new(["PATH", " "]),
Err(EnvironmentError::EmptyName)
);
}
#[test]
fn a_key_value_pair_is_refused_rather_than_split() -> TestResult {
let Err(error) = EnvironmentDeclaration::new(["PATH=/usr/bin"]) else {
return Err("a KEY=VALUE entry is not a pass-through name".to_owned());
};
assert_eq!(
error,
EnvironmentError::NameContainsEquals {
entry: "PATH=/usr/bin".to_owned()
}
);
assert!(
error.to_string().contains("PATH=/usr/bin"),
"the refusal quotes the entry: {error}"
);
Ok(())
}
#[test]
fn a_bare_program_without_path_in_the_declaration_is_refused_naming_path() -> TestResult {
let resolved = declaration(&["HOME"])?.resolve(&context(&[("HOME", "/u/t")]));
let Err(error) = resolved.require_for_program(Path::new("claude-code-acp")) else {
return Err("a bare program name cannot be found without PATH".to_owned());
};
let message = error.to_string();
assert!(message.contains("PATH"), "names the variable: {message}");
assert!(
message.contains("claude-code-acp"),
"names the program: {message}"
);
assert!(
message.contains("HOME"),
"names what IS declared: {message}"
);
assert!(
message.contains("env_pass"),
"names the declaration to correct: {message}"
);
Ok(())
}
#[test]
fn an_absolute_program_needs_no_path_so_a_hermetic_declaration_stays_legitimate() -> TestResult {
let resolved = declaration(&["HOME"])?.resolve(&context(&[("HOME", "/u/t")]));
assert_eq!(
resolved.require_for_program(Path::new("/usr/local/bin/claude-code-acp")),
Ok(())
);
Ok(())
}
#[test]
fn a_bare_program_with_path_declared_is_permitted() -> TestResult {
let resolved = declaration(&["PATH"])?.resolve(&context(&[("PATH", "/usr/bin")]));
assert_eq!(resolved.require_for_program(Path::new("norn")), Ok(()));
Ok(())
}
#[test]
fn a_relative_multi_component_program_is_not_gated_on_path() -> TestResult {
let resolved = declaration(&["HOME"])?.resolve(&context(&[("HOME", "/u/t")]));
assert_eq!(
resolved.require_for_program(Path::new("./bin/agent")),
Ok(())
);
Ok(())
}
#[test]
fn every_environment_fault_reaches_the_seam_as_a_deterministic_refusal() {
for error in [
EnvironmentError::EmptyName,
EnvironmentError::NameContainsEquals {
entry: "PATH=/usr/bin".to_owned(),
},
EnvironmentError::MissingForExec {
variable: "PATH",
program: "norn".to_owned(),
declared: "HOME".to_owned(),
},
] {
let rendered = error.to_string();
let mapped: crate::HarnessError = error.into();
assert!(
matches!(mapped, crate::HarnessError::Configuration { .. }),
"the launch was refused by a value in the document, and the next attempt reads \
the same document: {mapped:?}"
);
assert!(
mapped.is_deterministic(),
"a config refusal that presented as retryable would spend the whole attempt \
budget confirming a wall that cannot move: {mapped:?}"
);
assert!(
mapped.to_string().contains(&rendered),
"the seam error carries the declaration fault's own words: {mapped}"
);
}
}
#[test]
fn an_empty_declaration_renders_as_words_in_a_refusal() -> TestResult {
let empty = ChildEnvironment {
declared: Vec::new(),
pairs: Vec::new(),
absent: Vec::new(),
};
let Err(error) = empty.require_for_program(Path::new("agent")) else {
return Err("an empty environment cannot resolve a bare program name".to_owned());
};
assert!(
error.to_string().contains("no variables"),
"an empty declaration reads as words, never as an empty list: {error}"
);
Ok(())
}
#[test]
fn the_process_snapshot_really_reads_this_process() {
let snapshot = super::process_environment();
assert!(
snapshot.contains_key("PATH"),
"a process with no PATH could not have run this test at all"
);
}