use super::*;
use crate::agent::{Agent, RunOpts};
use crate::paths::AidHomeGuard;
use tempfile::TempDir;
#[test]
fn build_command_includes_external_context_directories() {
let temp = TempDir::new().unwrap();
let repo = temp.path().join("repo");
let shared_dir = temp.path().join("shared");
std::fs::create_dir_all(&repo).unwrap();
let opts = RunOpts {
dir: Some(repo.to_string_lossy().into_owned()),
output: None,
result_file: None,
model: None,
budget: false,
read_only: false,
context_files: vec![
repo.join("src/main.rs").to_string_lossy().into_owned(),
shared_dir.join("a.md").to_string_lossy().into_owned(),
shared_dir.join("b.md").to_string_lossy().into_owned(),
"../sibling/config.toml".to_string(),
],
session_id: None,
env: None,
env_forward: None,
};
let cmd = GeminiAgent.build_command("test", &opts).unwrap();
let args: Vec<String> = cmd.get_args().map(|arg| arg.to_string_lossy().into_owned()).collect();
let include_dirs: Vec<&str> = args
.windows(2)
.filter_map(|pair| (pair[0] == "--include-directories").then_some(pair[1].as_str()))
.collect();
let shared_dir = shared_dir.to_string_lossy().into_owned();
assert_eq!(include_dirs.len(), 2);
assert!(include_dirs.contains(&"../sibling"));
assert!(include_dirs.contains(&shared_dir.as_str()));
}
#[test]
fn build_command_sets_trust_workspace_env_by_default() {
let opts = RunOpts {
dir: None,
output: None,
result_file: None,
model: None,
budget: false,
read_only: false,
context_files: vec![],
session_id: None,
env: None,
env_forward: None,
};
struct EnvGuard {
prev: Option<std::ffi::OsString>,
}
impl Drop for EnvGuard {
fn drop(&mut self) {
unsafe {
match self.prev.take() {
Some(v) => std::env::set_var("GEMINI_CLI_TRUST_WORKSPACE", v),
None => std::env::remove_var("GEMINI_CLI_TRUST_WORKSPACE"),
}
}
}
}
let _guard = EnvGuard {
prev: std::env::var_os("GEMINI_CLI_TRUST_WORKSPACE"),
};
unsafe {
std::env::remove_var("GEMINI_CLI_TRUST_WORKSPACE");
}
let cmd = GeminiAgent.build_command("test", &opts).unwrap();
let env_pair = cmd
.get_envs()
.find(|(key, _)| *key == std::ffi::OsStr::new("GEMINI_CLI_TRUST_WORKSPACE"));
let value = env_pair
.and_then(|(_, v)| v)
.map(|v| v.to_string_lossy().into_owned());
assert_eq!(value.as_deref(), Some("true"));
}
#[test]
fn build_command_respects_pre_existing_trust_workspace_override() {
let opts = RunOpts {
dir: None,
output: None,
result_file: None,
model: None,
budget: false,
read_only: false,
context_files: vec![],
session_id: None,
env: None,
env_forward: None,
};
struct EnvGuard {
prev: Option<std::ffi::OsString>,
}
impl Drop for EnvGuard {
fn drop(&mut self) {
unsafe {
match self.prev.take() {
Some(v) => std::env::set_var("GEMINI_CLI_TRUST_WORKSPACE", v),
None => std::env::remove_var("GEMINI_CLI_TRUST_WORKSPACE"),
}
}
}
}
let _guard = EnvGuard {
prev: std::env::var_os("GEMINI_CLI_TRUST_WORKSPACE"),
};
unsafe {
std::env::set_var("GEMINI_CLI_TRUST_WORKSPACE", "false");
}
let cmd = GeminiAgent.build_command("test", &opts).unwrap();
let explicit = cmd
.get_envs()
.find(|(key, _)| *key == std::ffi::OsStr::new("GEMINI_CLI_TRUST_WORKSPACE"));
assert!(explicit.is_none());
}
#[test]
fn build_command_skips_default_model_for_auto_routing() {
let opts = RunOpts {
dir: None,
output: None,
result_file: None,
model: None,
budget: false,
read_only: false,
context_files: vec![],
session_id: None,
env: None,
env_forward: None,
};
let cmd = GeminiAgent.build_command("test", &opts).unwrap();
let args: Vec<String> = cmd.get_args().map(|arg| arg.to_string_lossy().into_owned()).collect();
assert!(!args.iter().any(|arg| arg == "-m"));
}
#[test]
fn parses_skill_and_hook_events_as_milestones() {
let task_id = TaskId::generate();
let skill = serde_json::json!({
"type": "skill_execute",
"name": "repo-map",
"status": "running"
});
let hook = serde_json::json!({
"type": "hook",
"message": "Post-action checks passed"
});
let skill_event = parse_stream_event(&task_id, &skill, Local::now()).unwrap();
let hook_event = parse_stream_event(&task_id, &hook, Local::now()).unwrap();
assert_eq!(skill_event.event_kind, EventKind::Milestone);
assert_eq!(skill_event.detail, "skill repo-map: running");
assert_eq!(hook_event.event_kind, EventKind::Milestone);
assert_eq!(hook_event.detail, "hook: Post-action checks passed");
}
#[test]
fn parses_gemini_rate_limit_errors() {
let temp = TempDir::new().unwrap();
let _guard = AidHomeGuard::set(temp.path());
let task_id = TaskId::generate();
let json = serde_json::json!({
"type": "error",
"message": "resourceExhausted: RATE_LIMIT_EXCEEDED"
});
let event = parse_stream_event(&task_id, &json, Local::now()).unwrap();
assert_eq!(event.event_kind, EventKind::Error);
assert!(crate::rate_limit::is_rate_limited(&AgentKind::Gemini));
}
#[test]
fn extract_response_handles_content_arrays_and_tool_boundaries() {
let output = r#"{"type":"message","role":"assistant","content":[{"type":"text","text":"Alpha"}],"delta":true}
{"type":"message","role":"assistant","content":[{"type":"text","text":" beta"}],"delta":true}
{"type":"tool_call","name":"Read","arguments":{"file":"src/main.rs"}}
{"type":"message","role":"assistant","content":[{"type":"text","text":"Gamma"}],"delta":true}
{"type":"result","status":"success"}"#;
let result = extract_response(output);
assert_eq!(result, Some("Alpha beta\n\nGamma".to_string()));
}