use super::*;
use super::validate_command_preflight_with;
use std::sync::Arc;
fn isolated_home() -> crate::paths::AidHomeGuard {
let temp = tempfile::tempdir().unwrap();
crate::paths::AidHomeGuard::set(temp.path())
}
#[test]
fn prepare_dispatch_accepts_qwen_read_only() {
let _guard = isolated_home();
let store = Arc::new(Store::open_memory().unwrap());
let mut args = RunArgs {
agent_name: "qwen".to_string(),
prompt: "Investigate a concrete task routing bug.".to_string(),
read_only: true,
existing_task_id: Some(TaskId("t-preflight-ro".to_string())),
..Default::default()
};
let prepared = super::prepare_dispatch_with(&store, &mut args, |_| true)
.expect("qwen --read-only should pass command preflight");
assert_eq!(prepared.task.id.as_str(), "t-preflight-ro");
}
#[test]
fn validate_command_preflight_accepts_codex_read_only_with_result_file() {
let agent = crate::agent::get_agent(AgentKind::Codex);
let args = RunArgs {
agent_name: "codex".to_string(),
prompt: "Audit the module and write findings.".to_string(),
read_only: true,
result_file: Some("result.md".to_string()),
..Default::default()
};
validate_command_preflight_with(agent.as_ref(), &args, None, |_| true).unwrap();
}
#[test]
fn validate_command_preflight_rejects_missing_resolved_binary() {
let agent = crate::agent::get_agent(AgentKind::Kilo);
let args = RunArgs {
agent_name: "kilo".to_string(),
prompt: "Implement a focused change with enough context.".to_string(),
..Default::default()
};
let err = validate_command_preflight_with(agent.as_ref(), &args, None, |_| false)
.unwrap_err()
.to_string();
assert!(
err.contains("binary 'kilo' missing from PATH"),
"unexpected error: {err}"
);
}
#[test]
fn validate_command_preflight_rejects_missing_codex_binary() {
let agent = crate::agent::get_agent(AgentKind::Codex);
let args = RunArgs {
agent_name: "codex".to_string(),
prompt: "Inspect the repository state carefully.".to_string(),
..Default::default()
};
let err = validate_command_preflight_with(agent.as_ref(), &args, None, |_| false)
.unwrap_err()
.to_string();
assert_eq!(
err,
"Agent 'codex' not found: binary 'codex' missing from PATH"
);
}
#[test]
fn validate_command_preflight_skips_path_probe_on_dry_run() {
let agent = crate::agent::get_agent(AgentKind::Codex);
let args = RunArgs {
agent_name: "codex".to_string(),
prompt: "Inspect the repository state carefully.".to_string(),
dry_run: true,
..Default::default()
};
validate_command_preflight_with(agent.as_ref(), &args, None, |_| false).unwrap();
}
#[test]
fn validate_command_preflight_rejects_agy_effective_model_without_model_flag() {
let agent = crate::agent::get_agent(AgentKind::Antigravity);
let args = RunArgs { agent_name: "agy".into(), ..Default::default() };
let error = validate_command_preflight_with(agent.as_ref(), &args, Some("gemini-3-pro"), |_| true)
.unwrap_err().to_string();
assert!(error.contains("does not support --model"), "{error}");
assert!(error.contains("gemini-3-pro"), "{error}");
}
#[test]
fn validate_command_preflight_accepts_agy_without_requested_model() {
let agent = crate::agent::get_agent(AgentKind::Antigravity);
let args = RunArgs { agent_name: "agy".into(), ..Default::default() };
validate_command_preflight_with(agent.as_ref(), &args, None, |_| true).unwrap();
}
#[test]
fn validate_command_preflight_skips_agy_model_check_on_dry_run() {
let agent = crate::agent::get_agent(AgentKind::Antigravity);
let args = RunArgs { agent_name: "agy".into(), dry_run: true, ..Default::default() };
validate_command_preflight_with(agent.as_ref(), &args, Some("gemini-3-pro"), |_| false).unwrap();
}
#[test]
fn prepare_dispatch_rejects_custom_agent_with_missing_binary_before_task_exists() {
let temp = tempfile::tempdir().unwrap();
let _guard = crate::paths::AidHomeGuard::set(temp.path());
crate::paths::ensure_dirs().unwrap();
let agents_dir = crate::paths::aid_dir().join("agents");
std::fs::create_dir_all(&agents_dir).unwrap();
std::fs::write(
agents_dir.join("goose.toml"),
r#"
[agent]
id = "goose"
display_name = "Goose"
command = "definitely-not-on-path-goose-bin"
"#,
)
.unwrap();
let store = Arc::new(Store::open_memory().unwrap());
let mut args = RunArgs {
agent_name: "goose".to_string(),
prompt: "Investigate a concrete dispatch failure path.".to_string(),
existing_task_id: Some(TaskId("t-preflight-goose".to_string())),
..Default::default()
};
let err = match prepare_dispatch(&store, &mut args) {
Ok(_) => panic!("missing custom binary must be refused before task creation"),
Err(err) => err.to_string(),
};
assert!(
err.contains("binary 'definitely-not-on-path-goose-bin' missing from PATH"),
"unexpected error: {err}"
);
assert!(
store.get_task("t-preflight-goose").unwrap().is_none(),
"missing-binary dispatch must not create a task row"
);
}
#[test]
fn foreign_agent_binary_blocks_cursor_in_advise_and_preflight_alike() {
let _permit = crate::test_subprocess::acquire();
let bin_dir = tempfile::tempdir().unwrap();
let agent = bin_dir.path().join("agent");
std::fs::write(&agent, "#!/bin/sh\necho 'Grok Build TUI'\nexit 0\n").unwrap();
use std::os::unix::fs::PermissionsExt;
std::fs::set_permissions(&agent, std::fs::Permissions::from_mode(0o755)).unwrap();
let helper = "cmd::run::run_dispatch_prepare::preflight_tests::reports_cursor_route_for_subprocess";
let output = std::process::Command::new(std::env::current_exe().unwrap())
.args(["--exact", helper, "--ignored", "--nocapture"])
.env("PATH", bin_dir.path())
.output()
.unwrap();
let stdout = String::from_utf8_lossy(&output.stdout);
assert!(output.status.success(), "helper failed: {stdout}{}", String::from_utf8_lossy(&output.stderr));
let marker = |key: &str| {
stdout.lines().find_map(|line| line.strip_prefix(key)).unwrap_or("<missing>").to_string()
};
let reason = "binary 'cursor-agent' missing from PATH";
assert_eq!(marker("ADVISE_INSTALLED="), "false");
assert_eq!(marker("ADVISE_ELIGIBLE="), "false");
assert_eq!(marker("ADVISE_REASON="), format!("not installed: {reason}"));
assert_eq!(marker("DETECTED="), "false");
assert_eq!(marker("PREFLIGHT="), format!("Agent 'cursor' not found: {reason}"));
}
#[test]
#[ignore]
fn reports_cursor_route_for_subprocess() {
let temp = tempfile::tempdir().unwrap();
let _home = crate::paths::AidHomeGuard::set(temp.path());
let declared = crate::types::DeclaredTaskProfile {
difficulty: crate::types::TaskDifficulty::Moderate,
budget: crate::types::TaskBudget::Standard,
urgency: crate::types::TaskUrgency::Normal,
rigor: crate::types::TaskRigor::Standard,
};
let report = crate::agent::selection::advise("refactor the parser", declared, None, None, None, 0, None);
let cursor = report.candidates.iter().find(|item| item.agent == "cursor").unwrap();
println!("ADVISE_INSTALLED={}", cursor.installed);
println!("ADVISE_ELIGIBLE={}", cursor.eligible);
let reason = cursor.exclusion_reason.clone().unwrap_or_default();
println!("ADVISE_REASON={}", reason.split("; ").next().unwrap_or_default());
println!("DETECTED={}", crate::agent::detect_agents().contains(&AgentKind::Cursor));
let agent = crate::agent::get_agent(AgentKind::Cursor);
let args = RunArgs {
agent_name: "cursor".to_string(),
prompt: "Implement a focused change with enough context.".to_string(),
..Default::default()
};
let err = validate_command_preflight_with(agent.as_ref(), &args, None, crate::agent::env::which_exists)
.unwrap_err();
println!("PREFLIGHT={err}");
}