use super::{ProviderKind, ProviderOptions, SameThreadContinuation, resolve};
use std::path::Path;
fn argv_of(cmd: &tokio::process::Command) -> Vec<String> {
let std_cmd = cmd.as_std();
std::iter::once(std_cmd.get_program().to_string_lossy().to_string())
.chain(std_cmd.get_args().map(|s| s.to_string_lossy().to_string()))
.collect()
}
#[test]
fn kind_parse_round_trip() {
for (input, expected) in [
("claude", ProviderKind::Claude),
("CLAUDE", ProviderKind::Claude),
("claude-code", ProviderKind::Claude),
("codex", ProviderKind::Codex),
("copilot", ProviderKind::Copilot),
("gh-copilot", ProviderKind::Copilot),
("github-copilot", ProviderKind::Copilot),
] {
assert_eq!(ProviderKind::parse(input), Some(expected));
}
assert_eq!(ProviderKind::parse("gpt-4"), None);
assert_eq!(ProviderKind::parse(""), None);
}
#[test]
fn claude_command_includes_expected_flags() {
let provider = resolve(ProviderKind::Claude);
let opts = ProviderOptions {
allowed_tools: vec!["read".to_string(), "bash".to_string()],
model: Some("sonnet".to_string()),
system_prompt: Some("be careful".to_string()),
agent_name: Some("frontend-specialist".to_string()),
session_id: Some("abc123".to_string()),
continue_session: false,
max_budget: Some(0.25),
worktree_name: Some("wt-1".to_string()),
claude_stream_json: false,
codex_json: false,
};
let cmd = provider.build_command("do the thing", Path::new("/tmp"), &opts);
let argv = argv_of(&cmd);
assert_eq!(argv[0], "claude");
assert!(argv.iter().any(|a| a == "-p"));
assert!(argv.iter().any(|a| a == "do the thing"));
assert!(argv.iter().any(|a| a == "--dangerously-skip-permissions"));
assert!(argv.iter().any(|a| a == "--agent"));
assert!(argv.iter().any(|a| a == "frontend-specialist"));
assert!(argv.iter().any(|a| a == "--session-id"));
assert!(argv.iter().any(|a| a == "abc123"));
assert!(argv.iter().any(|a| a == "--allowed-tools"));
assert!(argv.iter().any(|a| a == "Read,Bash"));
assert!(argv.iter().any(|a| a == "--model"));
assert!(argv.iter().any(|a| a == "sonnet"));
assert!(argv.iter().any(|a| a == "--append-system-prompt"));
assert!(argv.iter().any(|a| a == "be careful"));
assert!(!argv.iter().any(|a| a == "--system-prompt"));
assert!(argv.iter().any(|a| a == "--max-budget-usd"));
assert!(argv.iter().any(|a| a == "--worktree"));
assert!(argv.iter().any(|a| a == "wt-1"));
}
#[test]
fn claude_command_switches_to_stream_json_when_requested() {
let provider = resolve(ProviderKind::Claude);
let opts = ProviderOptions {
claude_stream_json: true,
..Default::default()
};
let cmd = provider.build_command("hi", Path::new("/tmp"), &opts);
let argv = argv_of(&cmd);
assert!(argv.iter().any(|a| a == "stream-json"));
assert!(argv.iter().any(|a| a == "--verbose"));
assert!(!argv.iter().any(|a| a == "text"));
}
#[test]
fn provider_continuation_capabilities_are_explicit() {
assert_eq!(
resolve(ProviderKind::Claude).same_thread_continuation(),
SameThreadContinuation::ExplicitSessionId
);
assert_eq!(
resolve(ProviderKind::Codex).same_thread_continuation(),
SameThreadContinuation::ProviderAssignedId
);
assert_eq!(
resolve(ProviderKind::Copilot).same_thread_continuation(),
SameThreadContinuation::Unsupported
);
}
#[test]
fn codex_command_merges_system_prompt_and_has_no_session_flag() {
let provider = resolve(ProviderKind::Codex);
let opts = ProviderOptions {
model: Some("gpt-5".to_string()),
system_prompt: Some("SYS".to_string()),
..Default::default()
};
let cmd = provider.build_command("USER", Path::new("/tmp"), &opts);
let argv = argv_of(&cmd);
assert_eq!(argv[0], "codex");
assert_eq!(argv[1], "exec");
assert!(argv.iter().any(|a| a == "--sandbox"));
assert!(argv.iter().any(|a| a == "workspace-write"));
assert!(argv.iter().any(|a| a == "--skip-git-repo-check"));
let last = argv.last().expect("argv has last");
assert!(last.contains("SYS"));
assert!(last.contains("USER"));
assert!(argv.iter().any(|a| a == "--model"));
assert!(argv.iter().any(|a| a == "gpt-5"));
assert!(!argv.iter().any(|a| a == "--session"));
assert!(!argv.iter().any(|a| a == "resume"));
assert!(!argv.iter().any(|a| a == "--json"));
}
#[test]
fn codex_command_with_session_id_uses_resume_subcommand() {
let provider = resolve(ProviderKind::Codex);
let opts = ProviderOptions {
session_id: Some("thread-42".to_string()),
codex_json: true,
..Default::default()
};
let cmd = provider.build_command("CONTINUE", Path::new("/tmp"), &opts);
let argv = argv_of(&cmd);
assert_eq!(argv[0], "codex");
assert_eq!(argv[1], "exec");
assert_eq!(argv[2], "resume");
assert_eq!(argv[3], "thread-42");
assert!(argv.iter().any(|a| a == "--json"));
assert_eq!(argv.last().map(String::as_str), Some("CONTINUE"));
}
#[test]
fn copilot_provider_emits_error_command() {
let provider = resolve(ProviderKind::Copilot);
let cmd = provider.build_command("anything", Path::new("/tmp"), &ProviderOptions::default());
let argv = argv_of(&cmd);
assert_eq!(argv[0], "sh");
assert_eq!(argv[1], "-c");
assert!(argv[2].contains("not supported"));
assert!(argv[2].contains("exit 2"));
}