use super::*;
fn args(client: ClientKind, client_args: &[&str]) -> WithArgs {
WithArgs {
managed: false,
global: false,
undo: false,
non_interactive: false,
interactive: false,
extend_global_config: false,
isolated_config: false,
pick_model: false,
server: None,
local: false,
token: None,
token_stdin: false,
model: None,
label: None,
run_ttl_hours: 1,
run_max_requests: None,
client,
client_args: client_args.iter().map(OsString::from).collect(),
}
}
fn argv(client: ClientKind, client_args: &[&str]) -> Vec<String> {
rendered(&plan(&args(client, client_args), None, true))
}
fn rendered(launch: &Launch) -> Vec<String> {
launch
.arguments
.iter()
.map(|value| value.to_string_lossy().into_owned())
.collect()
}
#[test]
fn a_client_flag_does_not_turn_a_session_into_a_one_shot_run() {
for forwarded in [
&["--resume", "2a42a73e"][..],
&["--continue"],
&["--verbose"],
&["--debug"],
&["--add-dir", "/tmp"],
&["--dangerously-skip-permissions"],
] {
let rendered = argv(ClientKind::ClaudeCode, forwarded);
assert!(
!rendered.iter().any(|argument| argument == "--print"),
"{forwarded:?} must launch a session, not a batch run: {rendered:?}"
);
assert!(
rendered.ends_with(
&forwarded
.iter()
.map(ToString::to_string)
.collect::<Vec<_>>()
),
"the client's own arguments must still reach it: {rendered:?}"
);
}
}
#[test]
fn a_bare_positional_is_still_a_one_shot_prompt() {
let rendered = argv(ClientKind::ClaudeCode, &["fix the tests"]);
assert!(
rendered.iter().any(|argument| argument == "--print"),
"a prompt is a task: {rendered:?}"
);
assert_eq!(
argv(ClientKind::Codex, &["fix the tests"]).first(),
Some(&"exec".to_string())
);
}
#[test]
fn a_run_without_a_terminal_is_one_shot() {
let launch = plan(&args(ClientKind::ClaudeCode, &["--verbose"]), None, false);
assert!(rendered(&launch).iter().any(|value| value == "--print"));
assert!(
launch.note.is_none(),
"nothing was guessed: there is no terminal to hold a session"
);
}
#[test]
fn the_explicit_flags_win_over_the_rule() {
let mut interactive = args(ClientKind::ClaudeCode, &["fix the tests"]);
interactive.interactive = true;
assert!(
!rendered(&plan(&interactive, None, true))
.iter()
.any(|value| value == "--print")
);
let mut one_shot = args(ClientKind::ClaudeCode, &["--resume", "abc"]);
one_shot.non_interactive = true;
assert!(
rendered(&plan(&one_shot, None, true))
.iter()
.any(|value| value == "--print")
);
}
#[test]
fn an_ordinary_interactive_launch_says_nothing_of_its_own() {
for forwarded in [&["--verbose"][..], &["--model", "claude-opus-5"], &[]] {
let launch = plan(&args(ClientKind::ClaudeCode, forwarded), None, true);
assert!(
launch.note.is_none(),
"{forwarded:?} went as typed and needs no announcement: {:?}",
launch.note
);
}
assert!(
plan(
&args(ClientKind::ClaudeCode, &["fix the tests"]),
None,
true
)
.note
.is_none()
);
}
#[test]
fn a_bare_launch_names_no_model() {
for client in ClientKind::ALL {
if requires_a_model(client) || client.integration().model_arg.is_none() {
continue;
}
let rendered = argv(client, &[]);
assert!(
!rendered.iter().any(|argument| argument == "--model"),
"{client} was given a model nobody asked for: {rendered:?}"
);
}
}
#[test]
fn an_explicit_model_is_passed_through() {
let launch = plan(&args(ClientKind::ClaudeCode, &[]), Some("opus[1m]"), true);
assert!(
rendered(&launch)
.windows(2)
.any(|pair| pair == ["--model", "opus[1m]"]),
"{:?}",
rendered(&launch)
);
}
#[test]
fn a_client_that_cannot_start_without_a_model_still_gets_one() {
for client in [
ClientKind::Opencode,
ClientKind::QwenCode,
ClientKind::Agent,
] {
assert!(requires_a_model(client), "{client}");
}
for client in [
ClientKind::ClaudeCode,
ClientKind::Codex,
ClientKind::GeminiCli,
ClientKind::GrokCli,
] {
assert!(!requires_a_model(client), "{client}");
}
let launch = plan(&args(ClientKind::Opencode, &[]), Some("some-model"), true);
assert!(
rendered(&launch)
.windows(2)
.any(|pair| pair == ["--model", "link-assistant/some-model"]),
"opencode namespaces the id it was given: {:?}",
rendered(&launch)
);
}
#[test]
fn a_model_the_client_was_given_wins() {
let launch = plan(
&args(ClientKind::ClaudeCode, &["--model", "theirs"]),
Some("ours"),
true,
);
let rendered = rendered(&launch);
assert_eq!(
rendered.iter().filter(|value| *value == "--model").count(),
1,
"{rendered:?}"
);
assert!(
rendered.iter().any(|value| value == "theirs"),
"{rendered:?}"
);
}
#[test]
fn colliding_wrapper_flags_after_client_are_forwarded() {
let rendered = argv(ClientKind::Codex, &["--global", "hi"]);
assert!(rendered.ends_with(&["--global".to_string(), "hi".to_string()]));
assert_ne!(rendered.first().map(String::as_str), Some("exec"));
}
#[test]
fn explicit_separator_is_not_forwarded() {
let rendered = argv(ClientKind::Opencode, &["--", "run", "hi"]);
assert_eq!(rendered.first().map(String::as_str), Some("run"));
assert_eq!(
rendered.iter().filter(|arg| arg.as_str() == "run").count(),
1
);
}
#[test]
fn command_mode_word_inside_prompt_is_not_treated_as_the_subcommand() {
let rendered = argv(ClientKind::Opencode, &["explain", "run"]);
assert_eq!(rendered.first().map(String::as_str), Some("run"));
assert!(rendered.ends_with(&["explain".to_string(), "run".to_string()]));
}
#[test]
fn a_mode_argument_that_takes_a_prompt_is_not_placed_before_a_flag() {
for client in [
ClientKind::GeminiCli,
ClientKind::GrokCli,
ClientKind::QwenCode,
ClientKind::Agent,
] {
assert!(
client.integration().non_interactive_arg_takes_a_value,
"{client} spells its mode as a flag taking the prompt"
);
let mut one_shot = args(client, &["--yolo"]);
one_shot.non_interactive = true;
let launch = plan(&one_shot, None, true);
let rendered = rendered(&launch);
let mode = client
.integration()
.non_interactive_arg
.expect("these clients have one");
assert!(
!rendered
.windows(2)
.any(|pair| pair[0] == mode && pair[1] == "--yolo"),
"{client}: the user's flag was placed where the prompt value belongs: {rendered:?}"
);
assert!(
launch.note.is_some(),
"{client}: a mode that could not be applied must say so"
);
}
}
#[test]
fn a_prompt_still_gets_the_mode_argument() {
let launch = plan(&args(ClientKind::GeminiCli, &["fix the tests"]), None, true);
let rendered = rendered(&launch);
assert!(
rendered
.windows(2)
.any(|pair| pair == ["-p", "fix the tests"]),
"{rendered:?}"
);
}
#[test]
fn a_mode_the_user_already_spelled_is_not_repeated() {
let rendered = argv(ClientKind::ClaudeCode, &["-p", "hi"]);
assert!(
!rendered.iter().any(|value| value == "--print"),
"the mode was added on top of the user's own spelling: {rendered:?}"
);
assert_eq!(rendered, ["-p", "hi"], "{rendered:?}");
}
#[test]
fn the_clients_own_git_guard_is_left_alone() {
for forwarded in [&["fix the tests"][..], &["exec", "fix the tests"]] {
let rendered = argv(ClientKind::Codex, forwarded);
assert!(
!rendered
.iter()
.any(|value| value == "--skip-git-repo-check"),
"{forwarded:?}: {rendered:?}"
);
}
}
#[test]
fn the_per_run_token_outlives_an_ordinary_session() {
use clap::Parser as _;
let parsed = crate::cli::Cli::try_parse_from(["router", "with", "claude"])
.expect("a bare launch parses");
let Some(crate::cli::Command::With(args)) = parsed.command else {
panic!("with is the command");
};
assert!(
args.run_ttl_hours >= 12,
"a coding session routinely runs for hours; {} is short enough to expire in use",
args.run_ttl_hours
);
let parsed =
crate::cli::Cli::try_parse_from(["router", "with", "--run-ttl-hours", "2", "claude"])
.expect("the flag parses");
let Some(crate::cli::Command::With(args)) = parsed.command else {
panic!("with is the command");
};
assert_eq!(args.run_ttl_hours, 2, "an explicit lifetime is honoured");
}