use gate4agent::pipe::cli_builder;
use gate4agent::{CliTool, SpawnOptions};
fn program(cmd: &std::process::Command) -> &str {
cmd.get_program().to_str().unwrap()
}
fn args(cmd: &std::process::Command) -> Vec<&str> {
cmd.get_args().map(|a| a.to_str().unwrap()).collect()
}
fn opts(prompt: &str) -> SpawnOptions {
SpawnOptions {
prompt: prompt.to_string(),
..Default::default()
}
}
#[test]
fn claude_default_argv() {
let builder = cli_builder(CliTool::ClaudeCode);
let cmd = builder.build_command(&opts("hello"));
assert_eq!(program(&cmd), "claude");
assert_eq!(
args(&cmd),
&[
"-p",
"--output-format",
"stream-json",
"--verbose",
"--dangerously-skip-permissions",
],
"Claude default: prompt must NOT appear in argv (delivered via stdin)"
);
}
#[test]
fn claude_with_model() {
let builder = cli_builder(CliTool::ClaudeCode);
let cmd = builder.build_command(&SpawnOptions {
prompt: "hello".to_string(),
model: Some("claude-opus-4".to_string()),
..Default::default()
});
let got = args(&cmd);
assert!(
got.windows(2).any(|w| w == ["--model", "claude-opus-4"]),
"--model claude-opus-4 must appear in argv"
);
assert!(got.contains(&"-p"));
assert!(!got.contains(&"hello"), "prompt must not be in Claude argv");
}
#[test]
fn claude_with_resume() {
let builder = cli_builder(CliTool::ClaudeCode);
let cmd = builder.build_command(&SpawnOptions {
prompt: "hello".to_string(),
resume_session_id: Some("ses_abc123".to_string()),
..Default::default()
});
let got = args(&cmd);
assert!(got.contains(&"-p"), "-p must appear even when resuming");
assert!(
got.windows(2).any(|w| w == ["--resume", "ses_abc123"]),
"--resume ses_abc123 must appear in argv"
);
assert!(
!got.contains(&"--continue"),
"--continue must NOT appear when resume_session_id is set"
);
}
#[test]
fn claude_with_continue_last() {
let builder = cli_builder(CliTool::ClaudeCode);
let cmd = builder.build_command(&SpawnOptions {
prompt: "hello".to_string(),
continue_last: true,
..Default::default()
});
let got = args(&cmd);
assert!(got.contains(&"--continue"), "--continue must appear");
assert!(
!got.contains(&"--resume"),
"--resume must NOT appear when only continue_last=true"
);
}
#[test]
fn claude_with_system_prompt() {
let builder = cli_builder(CliTool::ClaudeCode);
let cmd = builder.build_command(&SpawnOptions {
prompt: "hello".to_string(),
append_system_prompt: Some("Be concise.".to_string()),
..Default::default()
});
let got = args(&cmd);
assert!(
got.windows(2).any(|w| w == ["--append-system-prompt", "Be concise."]),
"--append-system-prompt 'Be concise.' must appear in argv"
);
}
#[test]
fn claude_with_allowed_tools() {
let builder = cli_builder(CliTool::ClaudeCode);
let cmd = builder.build_command(&SpawnOptions {
prompt: "hello".to_string(),
allowed_tools: vec!["Edit".to_string(), "Read".to_string(), "Bash".to_string()],
..Default::default()
});
let got = args(&cmd);
let pos = got.iter().position(|a| *a == "--allowedTools");
assert!(pos.is_some(), "--allowedTools flag must appear");
assert_eq!(
got.get(pos.unwrap() + 1).copied(),
Some("Edit,Read,Bash"),
"--allowedTools value must be comma-joined"
);
}
#[test]
fn claude_with_permission_mode() {
let builder = cli_builder(CliTool::ClaudeCode);
let cmd = builder.build_command(&SpawnOptions {
prompt: "hello".to_string(),
permission_mode: Some("accept-all".to_string()),
..Default::default()
});
let got = args(&cmd);
assert!(
got.windows(2).any(|w| w == ["--permission-mode", "accept-all"]),
"--permission-mode accept-all must appear"
);
assert!(
!got.contains(&"--dangerously-skip-permissions"),
"--dangerously-skip-permissions must NOT appear when permission_mode is set"
);
}
#[test]
fn claude_with_mcp_config() {
let builder = cli_builder(CliTool::ClaudeCode);
let cmd = builder.build_command(&SpawnOptions {
prompt: "hello".to_string(),
mcp_config: Some(std::path::PathBuf::from("path.json")),
..Default::default()
});
let got = args(&cmd);
let pos = got.iter().position(|a| *a == "--mcp-config");
assert!(pos.is_some(), "--mcp-config must appear in argv");
assert!(
got.get(pos.unwrap() + 1)
.map(|v| v.contains("path.json"))
.unwrap_or(false),
"--mcp-config value must contain path.json"
);
}
#[test]
fn claude_with_max_turns() {
let builder = cli_builder(CliTool::ClaudeCode);
let cmd = builder.build_command(&SpawnOptions {
prompt: "hello".to_string(),
max_turns: Some(10),
..Default::default()
});
let got = args(&cmd);
assert!(
got.windows(2).any(|w| w == ["--max-turns", "10"]),
"--max-turns 10 must appear in argv"
);
}
#[test]
fn codex_default_argv() {
let builder = cli_builder(CliTool::Codex);
let cmd = builder.build_command(&opts("write rust"));
assert_eq!(program(&cmd), "codex");
let got = args(&cmd);
assert_eq!(got.first().copied(), Some("exec"), "must start with 'exec' subcommand");
assert!(got.contains(&"--json"), "--json must be present");
assert!(got.contains(&"--full-auto"), "--full-auto must be present");
assert_eq!(got.last().copied(), Some("write rust"), "prompt must be last");
}
#[test]
fn codex_with_model() {
let builder = cli_builder(CliTool::Codex);
let cmd = builder.build_command(&SpawnOptions {
prompt: "write rust".to_string(),
model: Some("o3".to_string()),
..Default::default()
});
let got = args(&cmd);
assert!(
got.windows(2).any(|w| w == ["--model", "o3"]),
"--model o3 must appear in argv"
);
assert_eq!(got.last().copied(), Some("write rust"), "prompt must be last");
}
#[test]
fn codex_with_resume_id() {
let builder = cli_builder(CliTool::Codex);
let cmd = builder.build_command(&SpawnOptions {
prompt: "continue".to_string(),
resume_session_id: Some("rollout-abc".to_string()),
..Default::default()
});
assert_eq!(program(&cmd), "codex");
let got = args(&cmd);
assert_eq!(&got[..3], &["exec", "resume", "rollout-abc"]);
assert!(got.contains(&"--json"));
assert!(got.contains(&"--full-auto"));
assert_eq!(got.last().copied(), Some("continue"));
}
#[test]
fn codex_with_continue_last() {
let builder = cli_builder(CliTool::Codex);
let cmd = builder.build_command(&SpawnOptions {
prompt: "continue".to_string(),
continue_last: true,
..Default::default()
});
assert_eq!(program(&cmd), "codex");
let got = args(&cmd);
assert_eq!(&got[..3], &["exec", "resume", "--last"]);
assert!(got.contains(&"--json"));
assert!(got.contains(&"--full-auto"));
assert_eq!(got.last().copied(), Some("continue"), "prompt must be last");
}
#[test]
fn gemini_default_argv() {
let builder = cli_builder(CliTool::Gemini);
let cmd = builder.build_command(&opts("explain lifetimes"));
assert_eq!(program(&cmd), "gemini");
assert_eq!(
args(&cmd),
&["--output-format", "stream-json", "-p", "explain lifetimes"],
"Gemini default: --output-format stream-json -p <prompt>, no --verbose"
);
}
#[test]
fn gemini_with_model_ignored() {
let builder = cli_builder(CliTool::Gemini);
let cmd = builder.build_command(&SpawnOptions {
prompt: "test".to_string(),
model: Some("gemini-2.5-pro".to_string()),
..Default::default()
});
let got = args(&cmd);
assert!(
got.windows(2).any(|w| w == ["--output-format", "stream-json"]),
"--output-format stream-json must be present"
);
assert!(got.contains(&"-p"), "-p must be present");
assert_eq!(got.last().copied(), Some("test"), "prompt must be last");
}
#[test]
fn gemini_with_resume() {
let builder = cli_builder(CliTool::Gemini);
let cmd = builder.build_command(&SpawnOptions {
prompt: "test".to_string(),
resume_session_id: Some("session-42".to_string()),
..Default::default()
});
let got = args(&cmd);
assert!(
got.windows(2).any(|w| w == ["--resume", "session-42"]),
"--resume session-42 must appear"
);
assert!(got.contains(&"-p"), "-p must still be present");
assert_eq!(got.last().copied(), Some("test"), "prompt must be last");
}
#[test]
fn gemini_with_sandbox() {
let builder = cli_builder(CliTool::Gemini);
let cmd = builder.build_command(&SpawnOptions {
prompt: "test".to_string(),
sandbox: true,
..Default::default()
});
let got = args(&cmd);
assert!(got.contains(&"--sandbox"), "--sandbox must appear when sandbox=true");
}
#[test]
fn gemini_no_sandbox_by_default() {
let builder = cli_builder(CliTool::Gemini);
let cmd = builder.build_command(&opts("test"));
assert!(!args(&cmd).contains(&"--sandbox"), "--sandbox must not appear by default");
}
#[test]
fn opencode_default_argv() {
let builder = cli_builder(CliTool::OpenCode);
let cmd = builder.build_command(&opts("write a function"));
assert_eq!(program(&cmd), "opencode");
let got = args(&cmd);
assert_eq!(got.first().copied(), Some("run"), "must start with 'run' subcommand");
assert!(
got.windows(2).any(|w| w == ["--format", "json"]),
"--format json must appear"
);
assert!(
got.windows(2).any(|w| w == ["-m", "opencode/gpt-5-nano"]),
"-m opencode/gpt-5-nano must appear (default free model)"
);
assert_eq!(
got.last().copied(),
Some("write a function"),
"prompt must be last"
);
assert!(!got.contains(&"--session"), "--session must not appear in fresh session");
assert!(!got.contains(&"--continue"), "--continue must not appear in fresh session");
}
#[test]
fn opencode_with_model() {
let builder = cli_builder(CliTool::OpenCode);
let cmd = builder.build_command(&SpawnOptions {
prompt: "hello".to_string(),
model: Some("anthropic/claude-opus-4".to_string()),
..Default::default()
});
let got = args(&cmd);
assert!(
got.windows(2).any(|w| w == ["-m", "anthropic/claude-opus-4"]),
"-m <model> must appear in OpenCode argv"
);
assert_eq!(got.last().copied(), Some("hello"), "prompt must be last");
}
#[test]
fn opencode_with_session_id() {
let builder = cli_builder(CliTool::OpenCode);
let cmd = builder.build_command(&SpawnOptions {
prompt: "continue".to_string(),
resume_session_id: Some("ses_ABCDEF".to_string()),
..Default::default()
});
let got = args(&cmd);
let pos = got.iter().position(|a| *a == "--session");
assert!(pos.is_some(), "--session must appear");
assert_eq!(
got.get(pos.unwrap() + 1).copied(),
Some("ses_ABCDEF"),
"--session value must be the session ID"
);
assert!(!got.contains(&"--continue"), "--continue must not appear when session ID is set");
assert_eq!(got.last().copied(), Some("continue"), "prompt must be last");
}
#[test]
fn opencode_with_continue_last() {
let builder = cli_builder(CliTool::OpenCode);
let cmd = builder.build_command(&SpawnOptions {
prompt: "continue".to_string(),
continue_last: true,
..Default::default()
});
let got = args(&cmd);
assert!(got.contains(&"--continue"), "--continue must appear when continue_last=true");
assert!(!got.contains(&"--session"), "--session must not appear when continue_last only");
assert_eq!(got.last().copied(), Some("continue"), "prompt must be last");
}