use super::Cli;
use clap::builder::ValueParser;
use clap::{CommandFactory, Parser};
fn walk(cmd: &clap::Command, path: &str, out: &mut Vec<(String, clap::Arg)>) {
for arg in cmd.get_arguments() {
out.push((path.to_string(), arg.clone()));
}
for sub in cmd.get_subcommands() {
walk(sub, &format!("{path} {}", sub.get_name()), out);
}
}
fn all_args() -> Vec<(String, clap::Arg)> {
let mut out = Vec::new();
let mut cli = Cli::command();
cli.build();
walk(&cli, "met", &mut out);
assert!(
out.len() > 150,
"the walk found only {} arguments",
out.len()
);
out
}
fn is_bool(arg: &clap::Arg) -> bool {
arg.get_value_parser().type_id() == ValueParser::bool().type_id()
}
#[test]
fn every_bool_flag_is_presence_only() {
let offenders: Vec<String> = all_args()
.iter()
.filter(|(_, a)| is_bool(a) && !matches!(a.get_action(), clap::ArgAction::SetTrue))
.map(|(p, a)| format!("{p} --{}", a.get_long().unwrap_or("?")))
.collect();
assert!(
offenders.is_empty(),
"boolean flags that take a value (make them ArgAction::SetTrue, named for the \
non-default state, or an auto/on/off enum): {offenders:?}"
);
}
#[test]
fn no_value_flag_can_be_given_without_its_value() {
let offenders: Vec<String> = all_args()
.iter()
.filter(|(_, a)| a.get_action().takes_values())
.filter(|(_, a)| a.get_long().is_some())
.filter(|(_, a)| a.get_num_args().is_some_and(|r| r.min_values() == 0))
.map(|(p, a)| format!("{p} --{}", a.get_long().unwrap_or("?")))
.collect();
assert!(
offenders.is_empty(),
"flags that accept being given without a value: {offenders:?}"
);
}
#[test]
fn a_serve_boolean_refuses_a_value() {
let serve = Cli::command();
let serve = serve
.find_subcommand("serve")
.expect("serve subcommand")
.clone();
let bools: Vec<String> = serve
.get_arguments()
.filter(|a| is_bool(a))
.filter_map(|a| a.get_long().map(str::to_string))
.collect();
assert!(
bools.len() > 25,
"only {} serve booleans found",
bools.len()
);
for flag in &bools {
let bare = ["met", "serve", "org/model", &format!("--{flag}")].map(String::from);
assert!(
Cli::try_parse_from(&bare).is_ok(),
"--{flag} bare must parse"
);
for value in ["true", "false"] {
let spaced = ["met", "serve", "org/model", &format!("--{flag}"), value];
assert!(
Cli::try_parse_from(spaced).is_err(),
"--{flag} {value} was accepted"
);
let joined = ["met", "serve", "org/model", &format!("--{flag}={value}")];
assert!(
Cli::try_parse_from(joined).is_err(),
"--{flag}={value} was accepted"
);
}
}
}