274 lines
8.1 KiB
Rust
274 lines
8.1 KiB
Rust
use std::path::{Path, PathBuf};
|
|
use std::time::Duration;
|
|
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn codex_preset_is_version_pinned() {
|
|
let launch = AcpAgentPreset::Codex.launch_config();
|
|
|
|
assert_eq!(launch.command, PathBuf::from("npx"));
|
|
assert_eq!(
|
|
launch.args,
|
|
vec![
|
|
"--yes",
|
|
&format!("@agentclientprotocol/codex-acp@{CODEX_ACP_NPM_VERSION}")
|
|
]
|
|
);
|
|
assert_eq!(
|
|
launch.env.get("DEFAULT_AUTH_REQUEST").map(String::as_str),
|
|
Some(r#"{"methodId":"chat-gpt"}"#)
|
|
);
|
|
assert_eq!(
|
|
launch.env.get("INITIAL_AGENT_MODE").map(String::as_str),
|
|
Some("read-only")
|
|
);
|
|
assert_eq!(
|
|
launch
|
|
.preferred_auth_method
|
|
.as_ref()
|
|
.map(ToString::to_string),
|
|
Some("chat-gpt".to_owned())
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn opencode_preset_is_version_pinned() {
|
|
let launch = AcpAgentPreset::OpenCode.launch_config();
|
|
|
|
assert_eq!(launch.command, PathBuf::from("npx"));
|
|
assert_eq!(
|
|
launch.args,
|
|
vec![
|
|
"--yes",
|
|
&format!("opencode-ai@{OPENCODE_NPM_VERSION}"),
|
|
"acp"
|
|
]
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn resolved_opencode_prefers_the_native_executable() {
|
|
let launch = AcpAgentPreset::OpenCode
|
|
.resolve_launch_config_with(|command| match command {
|
|
"opencode" => Some(PathBuf::from("/opt/bin/opencode")),
|
|
"npx" => Some(PathBuf::from("/opt/bin/npx")),
|
|
_ => None,
|
|
})
|
|
.unwrap();
|
|
|
|
assert_eq!(launch.command, PathBuf::from("/opt/bin/opencode"));
|
|
assert_eq!(launch.args, vec!["acp"]);
|
|
}
|
|
|
|
#[test]
|
|
fn resolved_codex_falls_back_to_bun_compatibility_mode() {
|
|
let resolve = |command: &str| (command == "bunx").then(|| PathBuf::from("/opt/bin/bunx"));
|
|
let codex = AcpAgentPreset::Codex
|
|
.resolve_launch_config_with(resolve)
|
|
.unwrap();
|
|
|
|
assert_eq!(codex.command, PathBuf::from("/opt/bin/bunx"));
|
|
assert_eq!(
|
|
codex.args,
|
|
vec![
|
|
"--bun".to_owned(),
|
|
format!("@agentclientprotocol/codex-acp@{CODEX_ACP_NPM_VERSION}")
|
|
]
|
|
);
|
|
assert_eq!(
|
|
codex.env.get("INITIAL_AGENT_MODE").map(String::as_str),
|
|
Some("read-only")
|
|
);
|
|
assert_eq!(
|
|
codex
|
|
.preferred_auth_method
|
|
.as_ref()
|
|
.map(ToString::to_string),
|
|
Some("chat-gpt".to_owned())
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn resolved_presets_explain_missing_launchers() {
|
|
let error = AcpAgentPreset::Codex
|
|
.resolve_launch_config_with(|_| None)
|
|
.unwrap_err();
|
|
|
|
assert!(error.contains("requires npx or bunx"));
|
|
|
|
let opencode_error = AcpAgentPreset::OpenCode
|
|
.resolve_launch_config_with(|command| {
|
|
(command == "bunx").then(|| PathBuf::from("/opt/bin/bunx"))
|
|
})
|
|
.unwrap_err();
|
|
assert!(opencode_error.contains("requires the opencode executable or npx"));
|
|
}
|
|
|
|
#[test]
|
|
fn launch_config_is_a_comparable_fingerprint() {
|
|
let first = AcpLaunchConfig::new("/usr/bin/npx")
|
|
.args(["agent", "acp"])
|
|
.env("TOKEN", "first")
|
|
.preferred_auth_method("browser");
|
|
let same = first.clone();
|
|
let different = first.clone().env("TOKEN", "second");
|
|
let different_auth = first.clone().preferred_auth_method("api-key");
|
|
|
|
assert_eq!(first, same);
|
|
assert_ne!(first, different);
|
|
assert_ne!(first, different_auth);
|
|
}
|
|
|
|
#[test]
|
|
fn child_environment_clears_credentials_and_preserves_runtime_paths() {
|
|
let parent = [
|
|
("PATH", "/usr/bin"),
|
|
("HOME", "/Users/test"),
|
|
("XDG_CONFIG_HOME", "/Users/test/.config"),
|
|
("OPENAI_API_KEY", "secret-openai-key"),
|
|
("AWS_SECRET_ACCESS_KEY", "secret-aws-key"),
|
|
("HTTPS_PROXY", "https://user:secret@example.com"),
|
|
("GALAXY_INTERNAL_SECRET", "secret-galaxy-value"),
|
|
("XDG_AGENT_TOKEN", "secret-xdg-value"),
|
|
("LC_AGENT_TOKEN", "secret-locale-value"),
|
|
]
|
|
.map(|(name, value)| (OsString::from(name), OsString::from(value)));
|
|
let explicit = BTreeMap::from([
|
|
("INITIAL_AGENT_MODE".to_owned(), "read-only".to_owned()),
|
|
(
|
|
"DEFAULT_AUTH_REQUEST".to_owned(),
|
|
r#"{"methodId":"chat-gpt"}"#.to_owned(),
|
|
),
|
|
]);
|
|
|
|
let overrides = sanitized_environment_overrides(parent, &explicit);
|
|
|
|
assert!(!overrides.contains_key("PATH"));
|
|
assert!(!overrides.contains_key("HOME"));
|
|
assert!(!overrides.contains_key("XDG_CONFIG_HOME"));
|
|
assert_eq!(
|
|
overrides.get("OPENAI_API_KEY").map(String::as_str),
|
|
Some("")
|
|
);
|
|
assert_eq!(
|
|
overrides.get("AWS_SECRET_ACCESS_KEY").map(String::as_str),
|
|
Some("")
|
|
);
|
|
assert_eq!(overrides.get("HTTPS_PROXY").map(String::as_str), Some(""));
|
|
assert_eq!(
|
|
overrides.get("GALAXY_INTERNAL_SECRET").map(String::as_str),
|
|
Some("")
|
|
);
|
|
assert_eq!(
|
|
overrides.get("XDG_AGENT_TOKEN").map(String::as_str),
|
|
Some("")
|
|
);
|
|
assert_eq!(
|
|
overrides.get("LC_AGENT_TOKEN").map(String::as_str),
|
|
Some("")
|
|
);
|
|
assert_eq!(
|
|
overrides.get("INITIAL_AGENT_MODE").map(String::as_str),
|
|
Some("read-only")
|
|
);
|
|
assert_eq!(
|
|
overrides.get("DEFAULT_AUTH_REQUEST").map(String::as_str),
|
|
Some(r#"{"methodId":"chat-gpt"}"#)
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn explicit_agent_environment_wins_over_scrubbing() {
|
|
let parent = [(
|
|
OsString::from("AGENT_AUTH_TOKEN"),
|
|
OsString::from("parent-secret"),
|
|
)];
|
|
let explicit = BTreeMap::from([("AGENT_AUTH_TOKEN".to_owned(), "explicit-value".to_owned())]);
|
|
|
|
let overrides = sanitized_environment_overrides(parent, &explicit);
|
|
|
|
assert_eq!(
|
|
overrides.get("AGENT_AUTH_TOKEN").map(String::as_str),
|
|
Some("explicit-value")
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn configured_commands_are_resolved_before_launch() {
|
|
let executable = std::env::current_exe().unwrap();
|
|
let launch = AcpLaunchConfig::new(&executable).resolve_command().unwrap();
|
|
|
|
assert_eq!(launch.command, executable);
|
|
}
|
|
|
|
#[test]
|
|
fn executable_resolution_uses_platform_extensions_in_order() {
|
|
let temp_dir = tempfile::TempDir::new().unwrap();
|
|
let command_path = temp_dir.path().join("npx.cmd");
|
|
std::fs::write(&command_path, "@echo off\r\n").unwrap();
|
|
|
|
let resolved = find_executable_in_directories(
|
|
"npx",
|
|
[temp_dir.path().to_owned()],
|
|
&[".exe".to_owned(), ".cmd".to_owned()],
|
|
);
|
|
|
|
assert_eq!(resolved, Some(command_path));
|
|
}
|
|
|
|
#[test]
|
|
fn executable_resolution_does_not_append_extensions_to_explicit_extension() {
|
|
let temp_dir = tempfile::TempDir::new().unwrap();
|
|
std::fs::write(temp_dir.path().join("agent.exe.cmd"), "@echo off\r\n").unwrap();
|
|
|
|
let resolved = find_executable_in_directories(
|
|
"agent.exe",
|
|
[temp_dir.path().to_owned()],
|
|
&[".cmd".to_owned()],
|
|
);
|
|
|
|
assert_eq!(resolved, None);
|
|
}
|
|
|
|
#[test]
|
|
fn windows_path_extensions_are_parsed_in_declared_order() {
|
|
let extensions = windows_executable_extensions(Some(std::ffi::OsStr::new(".COM;.EXE; .CMD;")));
|
|
|
|
assert_eq!(extensions, vec![".COM", ".EXE", ".CMD"]);
|
|
}
|
|
|
|
#[test]
|
|
fn windows_path_extensions_use_standard_fallback_when_missing_or_empty() {
|
|
let expected = vec![".COM", ".EXE", ".BAT", ".CMD"];
|
|
|
|
assert_eq!(windows_executable_extensions(None), expected);
|
|
assert_eq!(
|
|
windows_executable_extensions(Some(std::ffi::OsStr::new(" ; "))),
|
|
expected
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn codex_path_sets_the_adapter_environment_variable() {
|
|
let launch = AcpLaunchConfig::new("npx").codex_path(Path::new("/opt/codex"));
|
|
|
|
assert_eq!(
|
|
launch.env.get("CODEX_PATH").map(String::as_str),
|
|
Some("/opt/codex")
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn initialization_timeout_is_configurable_and_bounded_by_default() {
|
|
let default = AcpManagerConfig::new(AcpLaunchConfig::new("agent"));
|
|
let custom = AcpManagerConfig::new(AcpLaunchConfig::new("agent"))
|
|
.initialization_timeout(Duration::from_secs(2))
|
|
.authentication_timeout(Duration::from_secs(3));
|
|
|
|
assert_eq!(default.initialization_timeout, Duration::from_secs(30));
|
|
assert_eq!(default.authentication_timeout, Duration::from_secs(5 * 60));
|
|
assert_eq!(custom.initialization_timeout, Duration::from_secs(2));
|
|
assert_eq!(custom.authentication_timeout, Duration::from_secs(3));
|
|
}
|