314 lines
9.9 KiB
Rust
314 lines
9.9 KiB
Rust
use super::support::*;
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn installation_detection_separates_executables_from_version_metadata() {
|
|
for client in [
|
|
AgentClient::ClaudeCode,
|
|
AgentClient::Codex,
|
|
AgentClient::OpenCode,
|
|
AgentClient::OpenClaw,
|
|
AgentClient::Hermes,
|
|
AgentClient::DeepSeekHarness,
|
|
AgentClient::KimiCode,
|
|
AgentClient::GrokBuild,
|
|
AgentClient::Omp,
|
|
] {
|
|
assert!(agent_installation_detected(client, None, true, false));
|
|
assert!(!agent_installation_detected(client, None, false, false));
|
|
assert!(!agent_installation_detected(
|
|
client,
|
|
Some("1.0.0"),
|
|
false,
|
|
false
|
|
));
|
|
}
|
|
assert!(agent_installation_detected(
|
|
AgentClient::Codex,
|
|
None,
|
|
false,
|
|
true
|
|
));
|
|
assert!(agent_installation_detected(
|
|
AgentClient::ClaudeDesktop,
|
|
Some("1.0.0"),
|
|
false,
|
|
false,
|
|
));
|
|
}
|
|
|
|
#[test]
|
|
fn executable_directories_preserve_path_precedence_and_package_manager_locations() {
|
|
let home = agent_test_home("agent-search-environment");
|
|
let inherited = home.join("inherited");
|
|
let registered = home.join("registered");
|
|
let npm = home.join("custom-npm");
|
|
let uv = home.join("custom-uv");
|
|
let volta = home.join("custom-volta");
|
|
let directories = agent_executable_directories_from_environment(
|
|
&home,
|
|
|name| match name {
|
|
"PATH" => Some(env::join_paths([&inherited, &inherited]).unwrap()),
|
|
"NPM_CONFIG_PREFIX" => Some(npm.clone().into_os_string()),
|
|
"UV_TOOL_BIN_DIR" => Some(uv.clone().into_os_string()),
|
|
"VOLTA_HOME" => Some(volta.clone().into_os_string()),
|
|
"BUN_INSTALL" => Some(std::ffi::OsString::new()),
|
|
_ => None,
|
|
},
|
|
&[registered.clone(), inherited.clone()],
|
|
);
|
|
assert_eq!(&directories[..2], &[inherited.clone(), registered]);
|
|
assert_eq!(
|
|
directories
|
|
.iter()
|
|
.filter(|path| **path == inherited)
|
|
.count(),
|
|
1
|
|
);
|
|
let npm_bin = if cfg!(target_os = "windows") {
|
|
npm
|
|
} else {
|
|
npm.join("bin")
|
|
};
|
|
assert!(directories.contains(&npm_bin));
|
|
assert!(directories.contains(&uv));
|
|
assert!(directories.contains(&volta.join("bin")));
|
|
assert!(directories.iter().all(|path| path.is_absolute()));
|
|
fs::remove_dir_all(home).unwrap();
|
|
}
|
|
|
|
#[cfg(target_os = "windows")]
|
|
#[test]
|
|
fn windows_cli_search_skips_shell_stubs_and_finds_runnable_shims() {
|
|
let home = agent_test_home("agent-search-shims");
|
|
let first = home.join("first");
|
|
let second = home.join("second");
|
|
fs::create_dir_all(first.join("agent.exe")).unwrap();
|
|
fs::create_dir_all(&second).unwrap();
|
|
fs::write(first.join("agent"), "shell stub").unwrap();
|
|
fs::write(first.join("agent.ps1"), "powershell stub").unwrap();
|
|
let executable = second.join("agent.cmd");
|
|
fs::write(&executable, "@exit /b 1\r\n").unwrap();
|
|
assert_eq!(
|
|
find_named_agent_executable_in_directories(&[first, second], &["agent"]),
|
|
Some(executable.clone()),
|
|
);
|
|
let version = read_agent_version(&executable, &home);
|
|
assert_eq!(version, None);
|
|
assert!(agent_installation_detected(
|
|
AgentClient::ClaudeCode,
|
|
version.as_deref(),
|
|
agent_cli_executable(&executable),
|
|
false,
|
|
));
|
|
fs::remove_dir_all(home).unwrap();
|
|
}
|
|
|
|
#[cfg(unix)]
|
|
#[test]
|
|
fn unix_cli_search_requires_executable_permissions() {
|
|
use std::os::unix::fs::PermissionsExt;
|
|
let home = agent_test_home("agent-search-permissions");
|
|
let executable = home.join("agent");
|
|
fs::write(&executable, "exit 1\n").unwrap();
|
|
fs::set_permissions(&executable, fs::Permissions::from_mode(0o644)).unwrap();
|
|
assert_eq!(
|
|
find_named_agent_executable_in_directories(&[home.clone()], &["agent"]),
|
|
None
|
|
);
|
|
fs::set_permissions(&executable, fs::Permissions::from_mode(0o755)).unwrap();
|
|
assert_eq!(
|
|
find_named_agent_executable_in_directories(&[home.clone()], &["agent"]),
|
|
Some(executable)
|
|
);
|
|
fs::remove_dir_all(home).unwrap();
|
|
}
|
|
|
|
#[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",
|
|
"workbuddy",
|
|
"antigravity-cli",
|
|
"kimi-code",
|
|
"grok-build",
|
|
"pi",
|
|
] {
|
|
let paths = config_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",
|
|
"APPDATA",
|
|
"XDG_CONFIG_HOME",
|
|
"CODEX_HOME",
|
|
"HERMES_HOME",
|
|
"OPENCODE_CONFIG",
|
|
"KIMI_CODE_HOME",
|
|
"GROK_HOME",
|
|
"DSH_HOME",
|
|
"WORKBUDDY_CONFIG_DIR",
|
|
"CODEBUDDY_CONFIG_DIR",
|
|
"WORKBUDDY_INSTALL_DIR",
|
|
"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();
|
|
}
|
|
|
|
#[test]
|
|
fn zcode_installation_does_not_require_version_metadata() {
|
|
assert!(agent_installation_detected(
|
|
AgentClient::ZCode,
|
|
None,
|
|
true,
|
|
false,
|
|
));
|
|
assert!(!agent_installation_detected(
|
|
AgentClient::ZCode,
|
|
None,
|
|
false,
|
|
false,
|
|
));
|
|
let targets = agent_launch_targets(
|
|
AgentClient::ZCode,
|
|
Some(Path::new("ZCode.exe")),
|
|
None,
|
|
false,
|
|
);
|
|
assert_eq!(targets.len(), 1);
|
|
assert_eq!(targets[0].id, "app");
|
|
}
|
|
|
|
#[cfg(target_os = "windows")]
|
|
#[test]
|
|
fn zcode_windows_finds_custom_installations_from_registered_paths() {
|
|
let home = agent_test_home("zcode-custom-installation");
|
|
let directory = home.join("自定义应用 [桌面]/ZCode's directory");
|
|
let executable = directory.join("ZCode.exe");
|
|
fs::create_dir_all(&directory).unwrap();
|
|
fs::write(&executable, []).unwrap();
|
|
let icon = directory.join("uninstallerIcon.ico");
|
|
let uninstaller = directory.join("Uninstall ZCode.exe");
|
|
for (kind, value) in [
|
|
("executable", path_to_string(&executable)),
|
|
("executable", format!("\"{}\"", executable.display())),
|
|
("directory", path_to_string(&directory)),
|
|
("icon", path_to_string(&icon)),
|
|
("icon", format!("{},0", executable.display())),
|
|
("icon", format!("\"{}\",-123", executable.display())),
|
|
(
|
|
"uninstaller",
|
|
format!("\"{}\" /currentuser", uninstaller.display()),
|
|
),
|
|
(
|
|
"uninstaller",
|
|
format!("{} /allusers", uninstaller.display()),
|
|
),
|
|
] {
|
|
assert_eq!(
|
|
parse_windows_zcode_registration(kind, &value),
|
|
Some(executable.clone()),
|
|
"kind={kind}, value={value}",
|
|
);
|
|
}
|
|
fs::remove_dir_all(home).unwrap();
|
|
}
|
|
|
|
#[cfg(target_os = "windows")]
|
|
#[test]
|
|
fn zcode_windows_display_name_matches_installer_variants() {
|
|
assert!(windows_display_name_matches_zcode("ZCode"));
|
|
assert!(windows_display_name_matches_zcode("ZCode (64-bit)"));
|
|
assert!(windows_display_name_matches_zcode("ZCode Desktop"));
|
|
assert!(!windows_display_name_matches_zcode("ZCodeHelper"));
|
|
assert!(!windows_display_name_matches_zcode("MyZCode"));
|
|
}
|
|
|
|
#[cfg(target_os = "windows")]
|
|
#[test]
|
|
fn zcode_windows_skips_stale_and_unrelated_registration_entries() {
|
|
let home = agent_test_home("zcode-stale-registration");
|
|
let unrelated = home.join("Uninstall ZCode.exe");
|
|
fs::write(&unrelated, []).unwrap();
|
|
let executable = home.join("valid/ZCode.exe");
|
|
fs::create_dir_all(executable.parent().unwrap()).unwrap();
|
|
fs::write(&executable, []).unwrap();
|
|
for (kind, value) in [
|
|
("directory", path_to_string(&home)),
|
|
("executable", path_to_string(&unrelated)),
|
|
(
|
|
"executable",
|
|
path_to_string(&home.join("missing/ZCode.exe")),
|
|
),
|
|
("executable", "ZCode.exe".to_string()),
|
|
("executable", "\"unterminated".to_string()),
|
|
("unknown", path_to_string(&executable)),
|
|
] {
|
|
assert_eq!(
|
|
parse_windows_zcode_registration(kind, &value),
|
|
None,
|
|
"kind={kind}, value={value}",
|
|
);
|
|
}
|
|
assert_eq!(
|
|
parse_windows_zcode_registration("executable", &path_to_string(&executable)),
|
|
Some(executable),
|
|
);
|
|
fs::remove_dir_all(home).unwrap();
|
|
}
|
|
|
|
#[cfg(target_os = "windows")]
|
|
#[test]
|
|
fn zcode_windows_version_discovery_does_not_launch_the_application() {
|
|
let home = agent_test_home("zcode-version-no-launch");
|
|
let executable = home.join("zcode.cmd");
|
|
let marker = home.join("launched.txt");
|
|
fs::write(
|
|
&executable,
|
|
format!(
|
|
"@echo off\r\necho launched > \"{}\"\r\necho 1.0.0\r\n",
|
|
marker.display()
|
|
),
|
|
)
|
|
.unwrap();
|
|
assert_eq!(read_zcode_app_version(&executable), None);
|
|
assert!(
|
|
!marker.exists(),
|
|
"version discovery launched the application"
|
|
);
|
|
fs::remove_dir_all(home).unwrap();
|
|
}
|