use std::process::ExitCode;
use clap::{CommandFactory, Parser};
use super::{Cli, Command, DevCommand};
#[test]
fn offline_cache_scopes_parse_with_current_views_and_private_index() {
assert!(
Cli::try_parse_from([
"tau",
"agent",
"cache",
"agent",
"--include-descendants",
"--format",
"jsonl",
"--prompt",
"prompt"
])
.is_ok()
);
assert!(
Cli::try_parse_from([
"tau",
"session",
"cache",
"session",
"--state-dir",
"/tmp/state"
])
.is_ok()
);
assert!(Cli::try_parse_from(["tau", "agent", "cache", "bad.id"]).is_err());
assert!(
Cli::try_parse_from(["tau", "agent", "cache", "agent", "--index", "/tmp/index"]).is_ok()
);
assert!(Cli::try_parse_from(["tau", "agent", "cache", "agent", "--view", "geometry"]).is_ok());
}
#[test]
fn cache_evidence_exit_codes_are_not_generic_failures() {
assert_eq!(crate::CliError::CachePartial.exit_code(), ExitCode::from(3));
assert_eq!(crate::CliError::CacheInvalid.exit_code(), ExitCode::from(2));
}
#[test]
fn session_commands_parse_session_ids_at_the_clap_boundary() {
let agent_list = Cli::parse_from(["tau", "agent", "list", "session_A-1"]);
assert!(matches!(
agent_list.command,
Some(Command::Agent {
command: super::AgentCommand::List(super::AgentListArgs { ref session_id, .. }),
}) if session_id.as_str() == "session_A-1"
));
let dev_send = Cli::parse_from(["tau", "dev", "send", "session_A-1", "hello"]);
assert!(matches!(
dev_send.command,
Some(Command::Dev {
command: DevCommand::Send { ref session_id, ref line },
}) if session_id.as_str() == "session_A-1" && line.as_slice() == ["hello"]
));
for command in [
["tau", "agent", "list", "bad.id"].as_slice(),
["tau", "dev", "send", "bad.id", "hello"].as_slice(),
] {
let error = match Cli::try_parse_from(command) {
Ok(_) => panic!("invalid session id must fail"),
Err(error) => error,
};
assert!(
error.to_string().contains(
"invalid value 'bad.id' for '<SESSION_ID>': \
session id contains invalid byte 0x2e at byte offset 3"
),
"{command:?} must preserve the SessionId diagnostic: {error}"
);
}
}
#[test]
fn papercut_developer_commands_parse_with_their_documented_options() {
assert!(Cli::try_parse_from(["tau", "dev", "papercut", "list"]).is_ok());
assert!(Cli::try_parse_from(["tau", "dev", "papercut", "list", "--markdown"]).is_ok());
assert!(
Cli::try_parse_from([
"tau",
"dev",
"papercut",
"clear",
"--state-dir",
"/tmp/tau-state",
])
.is_ok()
);
let mut command = Cli::command();
let dev = command
.find_subcommand_mut("dev")
.expect("developer command");
let papercut = dev
.find_subcommand_mut("papercut")
.expect("papercut command");
assert!(papercut.find_subcommand_mut("list").is_some());
assert!(papercut.find_subcommand_mut("clear").is_some());
}
#[test]
fn attach_and_resume_are_subcommands_only() {
assert!(Cli::try_parse_from(["tau", "--attach"]).is_err());
assert!(Cli::try_parse_from(["tau", "--resume", "s1"]).is_err());
let attach = Cli::parse_from(["tau", "attach", "s1"]);
assert!(matches!(
attach.command,
Some(super::Command::Attach { session: Some(ref id) }) if id == "s1"
));
let resume = Cli::parse_from(["tau", "resume"]);
assert!(matches!(
resume.command,
Some(super::Command::Resume { session: None })
));
}
#[test]
fn serve_requires_exactly_one_explicit_session_mode() {
assert!(Cli::try_parse_from(["tau", "serve", "--session", "s1"]).is_err());
assert!(Cli::try_parse_from(["tau", "serve", "--existing"]).is_err());
assert!(
Cli::try_parse_from(["tau", "serve", "--session", "s1", "--create", "--existing",])
.is_err()
);
assert!(
Cli::try_parse_from([
"tau",
"serve",
"--session",
"s1",
"--create",
"--create-or-existing",
])
.is_err()
);
assert!(
Cli::try_parse_from([
"tau",
"serve",
"--session",
"s1",
"--existing",
"--create-or-existing",
])
.is_err()
);
let parsed = Cli::parse_from(["tau", "serve", "--session", "s1", "--existing"]);
assert!(matches!(
parsed.command,
Some(super::Command::Serve {
session,
create: false,
existing: true,
create_or_existing: false,
bootstrap_prompt_file: None,
bootstrap_id: None,
mirror_extension_stderr: false,
}) if session.as_str() == "s1"
));
let parsed = Cli::parse_from(["tau", "serve", "--session", "s2", "--create"]);
assert!(matches!(
parsed.command,
Some(super::Command::Serve {
session,
create: true,
existing: false,
create_or_existing: false,
bootstrap_prompt_file: None,
bootstrap_id: None,
mirror_extension_stderr: false,
}) if session.as_str() == "s2"
));
let parsed = Cli::parse_from(["tau", "serve", "--session", "s3", "--create-or-existing"]);
assert!(matches!(
parsed.command,
Some(super::Command::Serve {
session,
create: false,
existing: false,
create_or_existing: true,
bootstrap_prompt_file: None,
bootstrap_id: None,
mirror_extension_stderr: false,
}) if session.as_str() == "s3"
));
}
#[test]
fn extension_stderr_mirror_flag_is_serve_only_and_opt_in() {
let parsed = Cli::parse_from([
"tau",
"serve",
"--session",
"s1",
"--existing",
"--mirror-extension-stderr",
]);
assert!(matches!(
parsed.command,
Some(super::Command::Serve {
mirror_extension_stderr: true,
..
})
));
for command in [
vec!["tau", "--mirror-extension-stderr"],
vec!["tau", "resume", "--mirror-extension-stderr"],
vec!["tau", "attach", "s1", "--mirror-extension-stderr"],
vec!["tau", "component", "harness", "--mirror-extension-stderr"],
vec!["tau", "--prompt-stdin", "--mirror-extension-stderr"],
vec!["tau", "--ephemeral", "--mirror-extension-stderr"],
] {
assert!(
Cli::try_parse_from(command).is_err(),
"flag unexpectedly accepted outside serve"
);
}
}
#[test]
fn serve_bootstrap_requires_paired_source_and_valid_id() {
let base = ["tau", "serve", "--session", "s1", "--existing"];
assert!(
Cli::try_parse_from(
base.into_iter()
.chain(["--bootstrap-prompt-file", "prompt.md"])
)
.is_err()
);
assert!(
Cli::try_parse_from(base.into_iter().chain(["--bootstrap-id", "telegram-v1"])).is_err()
);
assert!(
Cli::try_parse_from(base.into_iter().chain([
"--bootstrap-prompt-file",
"-",
"--bootstrap-id",
"telegram_v1",
]))
.is_ok()
);
assert!(
Cli::try_parse_from(base.into_iter().chain([
"--bootstrap-prompt-file",
"-",
"--bootstrap-id",
"contains.dot",
]))
.is_err()
);
}
#[test]
fn targeted_startup_keeps_root_option_placement() {
let parsed = Cli::parse_from(["tau", "--ephemeral", "resume"]);
assert!(parsed.run.ephemeral);
assert!(matches!(
parsed.command,
Some(super::Command::Resume { session: None })
));
assert!(Cli::try_parse_from(["tau", "resume", "--ephemeral"]).is_err());
for option in [
"--role=engineer",
"-r=engineer",
"--profile=focused",
"--harness-config=agents.default_role=engineer",
"--provider-alias=current=codex-work",
"--model-alias=current=gpt-5.5",
"--enable-role=engineer",
"--disable-role=engineer",
"--disable-roles-all",
"--enable-extensions-all",
"--disable-extensions-all",
"--enable-extension=core-shell",
"--disable-extension=core-shell",
] {
assert!(
Cli::try_parse_from(["tau", "resume", option]).is_err(),
"{option} must remain root-only"
);
}
}
#[test]
fn provider_and_model_alias_flags_are_typed_and_repeatable() {
let parsed = Cli::parse_from([
"tau",
"--provider-alias",
"current=codex.work",
"--provider-alias",
"current=codex-personal",
"--model-alias",
"fast=org/qwen-fast",
]);
assert_eq!(parsed.harness.provider_alias.len(), 2);
assert_eq!(
parsed.harness.provider_alias[0].to.to_string(),
"codex.work"
);
assert_eq!(
parsed.harness.model_alias[0].to.to_string(),
"org/qwen-fast"
);
for invalid in [
["tau", "--provider-alias", "missing"],
["tau", "--provider-alias", "=target"],
["tau", "--provider-alias", "name="],
["tau", "--provider-alias", "bad/name=target"],
["tau", "--model-alias", "name="],
] {
assert!(
Cli::try_parse_from(invalid).is_err(),
"{invalid:?} must fail"
);
}
}
#[test]
fn short_role_option_matches_long_form_without_reclaiming_resume() {
let long = Cli::parse_from(["tau", "--role", "engineer", "resume"]);
let short = Cli::parse_from(["tau", "-r", "engineer", "resume"]);
assert_eq!(short.harness.role, long.harness.role);
assert!(matches!(
short.command,
Some(super::Command::Resume { session: None })
));
assert!(Cli::try_parse_from(["tau", "resume", "-r", "engineer"]).is_err());
assert!(Cli::try_parse_from(["tau", "--role", "engineer", "-r", "reviewer"]).is_err());
}
#[test]
fn root_help_documents_short_role_option() {
let help = Cli::command().render_long_help().to_string();
assert!(help.contains("-r, --role <ROLE>"));
}
#[test]
fn agent_unload_help_documents_operator_safety_contract() {
let mut command = Cli::command();
let unload = command
.find_subcommand_mut("agent")
.expect("agent command")
.find_subcommand_mut("unload")
.expect("unload command");
let help = unload.render_long_help().to_string();
assert!(help.contains("Only durable agents are supported"));
assert!(help.contains("without deleting its transcript or session history"));
assert!(help.contains("indeterminate"));
assert!(help.contains("retrying the same session and agent is safe"));
}