fix(tests): isolate agent configuration paths from user environment
Ignore inherited client configuration paths in test builds and enforce temporary-home boundaries for history fixtures. Validation: 435 Rust tests pass, including a child-process regression with inherited client environment overrides.
This commit is contained in:
1 parent
36d3181475
commit
0a019ad1d7
4 files changed
+89
-25
No files matched your search
@@ -3,6 +3,18 @@ use super::*;
|
||||
const AGENT_VERSION_PROBE_TIMEOUT: Duration = Duration::from_secs(5);
|
||||
const AGENT_VERSION_PROBE_POLL_INTERVAL: Duration = Duration::from_millis(20);
|
||||
|
||||
#[cfg(not(test))]
|
||||
fn agent_configuration_environment(name: &str) -> Option<PathBuf> {
|
||||
env::var_os(name)
|
||||
.map(PathBuf::from)
|
||||
.filter(|path| !path.as_os_str().is_empty())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
fn agent_configuration_environment(_: &str) -> Option<PathBuf> {
|
||||
None
|
||||
}
|
||||
|
||||
pub(crate) fn agent_config_paths(client: AgentClient, home: &Path) -> Vec<PathBuf> {
|
||||
match client {
|
||||
AgentClient::ClaudeCode => {
|
||||
@@ -89,23 +101,17 @@ pub(crate) fn opencode_config_path_from_environment(
|
||||
}
|
||||
|
||||
pub(crate) fn kimi_code_home(home: &Path) -> PathBuf {
|
||||
env::var_os("KIMI_CODE_HOME")
|
||||
.map(PathBuf::from)
|
||||
.filter(|path| !path.as_os_str().is_empty())
|
||||
agent_configuration_environment("KIMI_CODE_HOME")
|
||||
.unwrap_or_else(|| home.join(".kimi-code"))
|
||||
}
|
||||
|
||||
pub(crate) fn grok_build_home(home: &Path) -> PathBuf {
|
||||
env::var_os("GROK_HOME")
|
||||
.map(PathBuf::from)
|
||||
.filter(|path| !path.as_os_str().is_empty())
|
||||
agent_configuration_environment("GROK_HOME")
|
||||
.unwrap_or_else(|| home.join(".grok"))
|
||||
}
|
||||
|
||||
pub(crate) fn deepseek_harness_home(home: &Path) -> PathBuf {
|
||||
env::var_os("DSH_HOME")
|
||||
.map(PathBuf::from)
|
||||
.filter(|path| !path.as_os_str().is_empty())
|
||||
agent_configuration_environment("DSH_HOME")
|
||||
.unwrap_or_else(|| home.join(".dsh"))
|
||||
}
|
||||
|
||||
@@ -131,9 +137,7 @@ pub(crate) fn read_package_json_version(path: &Path) -> Option<String> {
|
||||
}
|
||||
|
||||
pub(crate) fn pi_agent_directory(home: &Path) -> PathBuf {
|
||||
env::var_os("PI_CODING_AGENT_DIR")
|
||||
.map(PathBuf::from)
|
||||
.filter(|path| !path.as_os_str().is_empty())
|
||||
agent_configuration_environment("PI_CODING_AGENT_DIR")
|
||||
.unwrap_or_else(|| home.join(".pi/agent"))
|
||||
}
|
||||
|
||||
@@ -683,9 +687,7 @@ pub(crate) fn remove_pi_package(executable: &Path, home: &Path) -> Result<(), St
|
||||
}
|
||||
|
||||
pub(crate) fn codex_configuration_directory(home: &Path) -> PathBuf {
|
||||
env::var_os("CODEX_HOME")
|
||||
.map(PathBuf::from)
|
||||
.filter(|path| !path.as_os_str().is_empty())
|
||||
agent_configuration_environment("CODEX_HOME")
|
||||
.unwrap_or_else(|| home.join(".codex"))
|
||||
}
|
||||
|
||||
@@ -786,15 +788,13 @@ pub(crate) fn claude_desktop_config_paths(_home: &Path) -> Vec<PathBuf> {
|
||||
};
|
||||
#[cfg(target_os = "windows")]
|
||||
{
|
||||
let local = env::var_os("LOCALAPPDATA")
|
||||
.map(PathBuf::from)
|
||||
let local = agent_configuration_environment("LOCALAPPDATA")
|
||||
.unwrap_or_else(|| _home.join("AppData/Local"));
|
||||
claude_desktop_config_paths_from_local_app_data(&local)
|
||||
}
|
||||
#[cfg(target_os = "linux")]
|
||||
let (normal, threep) = {
|
||||
let config_home = env::var_os("XDG_CONFIG_HOME")
|
||||
.map(PathBuf::from)
|
||||
let config_home = agent_configuration_environment("XDG_CONFIG_HOME")
|
||||
.filter(|path| path.is_absolute())
|
||||
.unwrap_or_else(|| _home.join(".config"));
|
||||
(config_home.join("Claude"), config_home.join("Claude-3p"))
|
||||
@@ -865,16 +865,12 @@ pub(crate) fn find_windows_claude_data_directory(
|
||||
}
|
||||
|
||||
pub(crate) fn hermes_agent_config_path(home: &Path) -> PathBuf {
|
||||
if let Some(directory) = env::var_os("HERMES_HOME")
|
||||
.map(PathBuf::from)
|
||||
.filter(|path| !path.as_os_str().is_empty())
|
||||
{
|
||||
if let Some(directory) = agent_configuration_environment("HERMES_HOME") {
|
||||
return directory.join("config.yaml");
|
||||
}
|
||||
#[cfg(target_os = "windows")]
|
||||
{
|
||||
env::var_os("LOCALAPPDATA")
|
||||
.map(PathBuf::from)
|
||||
agent_configuration_environment("LOCALAPPDATA")
|
||||
.unwrap_or_else(|| home.join("AppData/Local"))
|
||||
.join("hermes/config.yaml")
|
||||
}
|
||||
|
||||
@@ -65,6 +65,13 @@ pub(crate) struct HistoryPreview {
|
||||
}
|
||||
|
||||
pub(crate) fn history_paths(client: &str, home: &Path) -> Result<Vec<PathBuf>, String> {
|
||||
let paths = history_paths_inner(client, home)?;
|
||||
#[cfg(test)]
|
||||
assert!(paths.iter().all(|path| path.starts_with(home)));
|
||||
Ok(paths)
|
||||
}
|
||||
|
||||
fn history_paths_inner(client: &str, home: &Path) -> Result<Vec<PathBuf>, String> {
|
||||
if client == PI_AGENT_ID {
|
||||
return Ok(vec![
|
||||
pi_provider_config_path(home),
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use super::*;
|
||||
|
||||
mod agent_configuration;
|
||||
mod agent_paths;
|
||||
mod agent_state;
|
||||
mod agent_transactions;
|
||||
mod alias_edit;
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
use super::support::*;
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn configuration_paths_ignore_inherited_environment() {
|
||||
let home = agent_test_home("isolated-paths");
|
||||
for client in [
|
||||
"claude-code",
|
||||
"claude-desktop",
|
||||
"codex",
|
||||
"opencode",
|
||||
"openclaw",
|
||||
"hermes",
|
||||
"deepseek-harness",
|
||||
"zcode",
|
||||
"kimi-code",
|
||||
"grok-build",
|
||||
"pi",
|
||||
] {
|
||||
let paths = history_paths(client, &home).unwrap();
|
||||
assert!(!paths.is_empty(), "{client}");
|
||||
assert!(
|
||||
paths.iter().all(|path| path.starts_with(&home)),
|
||||
"{client}: {paths:?}"
|
||||
);
|
||||
}
|
||||
if env::var_os("CPA_PATH_ISOLATION_CHILD").is_none() {
|
||||
let outside = home.join("inherited-environment");
|
||||
let mut command = Command::new(env::current_exe().unwrap());
|
||||
command.args([
|
||||
"--exact",
|
||||
"tests::agent_paths::configuration_paths_ignore_inherited_environment",
|
||||
"--nocapture",
|
||||
]);
|
||||
command.env("CPA_PATH_ISOLATION_CHILD", "1");
|
||||
for variable in [
|
||||
"LOCALAPPDATA",
|
||||
"XDG_CONFIG_HOME",
|
||||
"CODEX_HOME",
|
||||
"HERMES_HOME",
|
||||
"OPENCODE_CONFIG",
|
||||
"KIMI_CODE_HOME",
|
||||
"GROK_HOME",
|
||||
"DSH_HOME",
|
||||
"PI_CODING_AGENT_DIR",
|
||||
] {
|
||||
command.env(variable, &outside);
|
||||
}
|
||||
configure_background_command(&mut command);
|
||||
let output = command.output().unwrap();
|
||||
assert!(
|
||||
output.status.success(),
|
||||
"{}\n{}",
|
||||
String::from_utf8_lossy(&output.stdout),
|
||||
String::from_utf8_lossy(&output.stderr)
|
||||
);
|
||||
assert!(!outside.exists());
|
||||
}
|
||||
fs::remove_dir_all(home).unwrap();
|
||||
}
|
||||
Reference in new issue
Block a user