171 lines
5.4 KiB
Rust
171 lines
5.4 KiB
Rust
use super::*;
|
|
|
|
#[test]
|
|
fn unknown_builtin_agent_ids_are_rejected() {
|
|
let error = resolve_acp_launch("mystery-agent", "", &[]).unwrap_err();
|
|
|
|
assert!(error.contains("Unknown ACP agent"));
|
|
}
|
|
|
|
#[test]
|
|
fn custom_agent_ids_are_allowed_with_an_explicit_executable() {
|
|
let executable = std::env::current_exe().unwrap();
|
|
let launch = resolve_acp_launch(
|
|
"my-agent",
|
|
executable.to_str().unwrap(),
|
|
&["--acp".to_owned()],
|
|
)
|
|
.unwrap();
|
|
|
|
assert_eq!(launch.command, executable);
|
|
assert_eq!(launch.args, vec!["--acp"]);
|
|
}
|
|
|
|
#[test]
|
|
fn acp_model_identity_names_the_agent_instead_of_a_provider_model() {
|
|
assert_eq!(acp_model_id(" Codex "), "acp:codex");
|
|
assert_eq!(acp_model_id("My-Agent"), "acp:my-agent");
|
|
}
|
|
|
|
#[test]
|
|
fn acp_selection_identity_uses_structured_values_in_key_order() {
|
|
let values = std::collections::BTreeMap::from([
|
|
("mode".to_owned(), serde_json::json!("sol")),
|
|
("model".to_owned(), serde_json::json!("gpt-5.4")),
|
|
]);
|
|
|
|
assert_eq!(
|
|
acp_selection_identity("Codex", &values),
|
|
r#"acp:codex:mode="sol":model="gpt-5.4""#
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn acp_selection_identity_distinguishes_boolean_values() {
|
|
let enabled =
|
|
std::collections::BTreeMap::from([("thinking".to_owned(), serde_json::json!(true))]);
|
|
let disabled =
|
|
std::collections::BTreeMap::from([("thinking".to_owned(), serde_json::json!(false))]);
|
|
|
|
assert_ne!(
|
|
acp_selection_identity("Codex", &enabled),
|
|
acp_selection_identity("Codex", &disabled)
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn launch_fingerprint_is_stable_and_sensitive_to_the_full_configuration() {
|
|
let executable = std::env::current_exe().unwrap();
|
|
let command = executable.to_str().unwrap();
|
|
let launch = AcpLaunchConfig::new(&executable)
|
|
.args(["serve"])
|
|
.env("INITIAL_AGENT_MODE", "read-only")
|
|
.preferred_auth_method("browser");
|
|
let baseline = effective_launch_fingerprint("custom", None, &launch, true);
|
|
|
|
assert_eq!(
|
|
baseline,
|
|
effective_launch_fingerprint("CUSTOM", None, &launch, true)
|
|
);
|
|
assert_ne!(
|
|
baseline,
|
|
effective_launch_fingerprint("custom", None, &launch.clone().args(["other"]), true,)
|
|
);
|
|
assert_ne!(
|
|
baseline,
|
|
effective_launch_fingerprint(
|
|
"custom",
|
|
None,
|
|
&launch.clone().env("INITIAL_AGENT_MODE", "workspace-write"),
|
|
true,
|
|
)
|
|
);
|
|
assert_ne!(
|
|
baseline,
|
|
effective_launch_fingerprint(
|
|
"custom",
|
|
None,
|
|
&launch.clone().preferred_auth_method("api-key"),
|
|
true,
|
|
)
|
|
);
|
|
assert_eq!(
|
|
acp_launch_fingerprint("custom", command, &["serve".to_owned()]),
|
|
acp_launch_fingerprint("CUSTOM", command, &["serve".to_owned()])
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn builtin_presets_ignore_custom_arguments_but_include_the_pinned_version() {
|
|
assert_eq!(
|
|
acp_launch_fingerprint("codex", "", &["ignored".to_owned()]),
|
|
acp_launch_fingerprint("codex", "", &["also-ignored".to_owned()])
|
|
);
|
|
|
|
let launch = AcpAgentPreset::Codex.launch_config();
|
|
assert_ne!(
|
|
effective_launch_fingerprint("codex", Some(CODEX_ACP_NPM_VERSION), &launch, false),
|
|
effective_launch_fingerprint("codex", Some("different-version"), &launch, false)
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn persisted_sessions_require_the_same_launch_identity() {
|
|
let executable = std::env::current_exe().unwrap();
|
|
let command = executable.to_str().unwrap();
|
|
let args = vec!["serve".to_owned()];
|
|
let launch = resolve_acp_launch("custom", command, &args).unwrap();
|
|
let backend = AcpConversationData {
|
|
provider_id: String::new(),
|
|
agent_id: "custom".to_owned(),
|
|
launch_fingerprint: acp_launch_fingerprint("custom", command, &args),
|
|
session_id: Some("session-123".to_owned()),
|
|
config_values: Default::default(),
|
|
};
|
|
|
|
assert!(validate_acp_launch_identity(&backend, "custom", command, &launch).is_ok());
|
|
assert!(
|
|
validate_acp_launch_identity(&backend, "other", command, &launch)
|
|
.unwrap_err()
|
|
.contains("configuration has changed")
|
|
);
|
|
let different_launch = launch.clone().args(["other"]);
|
|
assert!(
|
|
validate_acp_launch_identity(&backend, "custom", command, &different_launch)
|
|
.unwrap_err()
|
|
.contains("configuration has changed")
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn legacy_acp_sessions_fail_closed_without_a_launch_fingerprint() {
|
|
let backend = AcpConversationData {
|
|
provider_id: String::new(),
|
|
agent_id: "codex".to_owned(),
|
|
launch_fingerprint: String::new(),
|
|
session_id: Some("legacy-session".to_owned()),
|
|
config_values: Default::default(),
|
|
};
|
|
|
|
let launch = AcpAgentPreset::Codex.launch_config();
|
|
assert!(validate_acp_launch_identity(&backend, "codex", "", &launch)
|
|
.unwrap_err()
|
|
.contains("predates"));
|
|
}
|
|
|
|
#[test]
|
|
fn persisted_acp_conversations_respect_the_runtime_toggle() {
|
|
let error = validate_acp_dispatch(true, false, false).unwrap_err();
|
|
|
|
assert!(error.contains("currently disabled"));
|
|
}
|
|
|
|
#[test]
|
|
fn local_acp_processes_cannot_target_remote_terminals() {
|
|
let error = validate_acp_dispatch(true, true, true).unwrap_err();
|
|
|
|
assert!(error.contains("Wormhole or SSH"));
|
|
assert!(error.contains("start a new provider-backed conversation"));
|
|
assert!(validate_acp_dispatch(true, true, false).is_ok());
|
|
}
|