#![allow(non_snake_case)]
use clap::ArgMatches;
use standout::cli::handler::{CommandContext, Output};
use standout_macros::handler;
#[handler]
fn simple_flag(#[flag] verbose: bool) -> Result<bool, anyhow::Error> {
Ok(verbose)
}
#[test]
fn test_simple_flag_true() {
let matches = clap::Command::new("test")
.arg(
clap::Arg::new("verbose")
.short('v')
.action(clap::ArgAction::SetTrue),
)
.get_matches_from(vec!["test", "-v"]);
let ctx = CommandContext::default();
let result = simple_flag__handler(&matches, &ctx);
assert!(result.is_ok());
assert!(result.unwrap());
}
#[test]
fn test_simple_flag_false() {
let matches = clap::Command::new("test")
.arg(
clap::Arg::new("verbose")
.short('v')
.action(clap::ArgAction::SetTrue),
)
.get_matches_from(vec!["test"]);
let ctx = CommandContext::default();
let result = simple_flag__handler(&matches, &ctx);
assert!(result.is_ok());
assert!(!result.unwrap());
}
#[handler]
fn flag_with_name(#[flag(name = "show-all")] all: bool) -> Result<bool, anyhow::Error> {
Ok(all)
}
#[test]
fn test_flag_with_custom_name() {
let matches = clap::Command::new("test")
.arg(
clap::Arg::new("show-all")
.long("show-all")
.action(clap::ArgAction::SetTrue),
)
.get_matches_from(vec!["test", "--show-all"]);
let ctx = CommandContext::default();
let result = flag_with_name__handler(&matches, &ctx);
assert!(result.is_ok());
assert!(result.unwrap());
}
#[handler]
fn required_arg(#[arg] name: String) -> Result<String, anyhow::Error> {
Ok(format!("Hello, {}!", name))
}
#[test]
fn test_required_arg() {
let matches = clap::Command::new("test")
.arg(clap::Arg::new("name").required(true))
.get_matches_from(vec!["test", "world"]);
let ctx = CommandContext::default();
let result = required_arg__handler(&matches, &ctx);
assert!(result.is_ok());
assert_eq!(result.unwrap(), "Hello, world!");
}
#[handler]
fn optional_arg(#[arg] limit: Option<usize>) -> Result<String, anyhow::Error> {
match limit {
Some(n) => Ok(format!("Limit: {}", n)),
None => Ok("No limit".to_string()),
}
}
#[test]
fn test_optional_arg_present() {
let matches = clap::Command::new("test")
.arg(clap::Arg::new("limit").value_parser(clap::value_parser!(usize)))
.get_matches_from(vec!["test", "10"]);
let ctx = CommandContext::default();
let result = optional_arg__handler(&matches, &ctx);
assert!(result.is_ok());
assert_eq!(result.unwrap(), "Limit: 10");
}
#[test]
fn test_optional_arg_missing() {
let matches = clap::Command::new("test")
.arg(clap::Arg::new("limit").value_parser(clap::value_parser!(usize)))
.get_matches_from(vec!["test"]);
let ctx = CommandContext::default();
let result = optional_arg__handler(&matches, &ctx);
assert!(result.is_ok());
assert_eq!(result.unwrap(), "No limit");
}
#[handler]
fn vec_arg(#[arg] tags: Vec<String>) -> Result<usize, anyhow::Error> {
Ok(tags.len())
}
#[test]
fn test_vec_arg_multiple() {
let matches = clap::Command::new("test")
.arg(clap::Arg::new("tags").action(clap::ArgAction::Append))
.get_matches_from(vec!["test", "foo", "bar", "baz"]);
let ctx = CommandContext::default();
let result = vec_arg__handler(&matches, &ctx);
assert!(result.is_ok());
assert_eq!(result.unwrap(), 3);
}
#[test]
fn test_vec_arg_empty() {
let matches = clap::Command::new("test")
.arg(clap::Arg::new("tags").action(clap::ArgAction::Append))
.get_matches_from(vec!["test"]);
let ctx = CommandContext::default();
let result = vec_arg__handler(&matches, &ctx);
assert!(result.is_ok());
assert_eq!(result.unwrap(), 0);
}
#[handler]
fn arg_with_name(#[arg(name = "num")] count: usize) -> Result<usize, anyhow::Error> {
Ok(count * 2)
}
#[test]
fn test_arg_with_custom_name() {
let matches = clap::Command::new("test")
.arg(
clap::Arg::new("num")
.required(true)
.value_parser(clap::value_parser!(usize)),
)
.get_matches_from(vec!["test", "5"]);
let ctx = CommandContext::default();
let result = arg_with_name__handler(&matches, &ctx);
assert!(result.is_ok());
assert_eq!(result.unwrap(), 10);
}
#[handler]
fn multiple_params(
#[flag] verbose: bool,
#[arg] name: String,
#[arg] count: Option<usize>,
) -> Result<String, anyhow::Error> {
let count_str = count.map(|c| c.to_string()).unwrap_or("none".to_string());
Ok(format!(
"verbose={}, name={}, count={}",
verbose, name, count_str
))
}
#[test]
fn test_multiple_params() {
let matches = clap::Command::new("test")
.arg(
clap::Arg::new("verbose")
.short('v')
.action(clap::ArgAction::SetTrue),
)
.arg(clap::Arg::new("name").required(true))
.arg(clap::Arg::new("count").value_parser(clap::value_parser!(usize)))
.get_matches_from(vec!["test", "-v", "alice", "42"]);
let ctx = CommandContext::default();
let result = multiple_params__handler(&matches, &ctx);
assert!(result.is_ok());
assert_eq!(result.unwrap(), "verbose=true, name=alice, count=42");
}
#[handler]
fn with_context(#[ctx] ctx: &CommandContext) -> Result<usize, anyhow::Error> {
Ok(ctx.command_path.len())
}
#[test]
fn test_with_context() {
let matches = clap::Command::new("test").get_matches_from(vec!["test"]);
let ctx = CommandContext::default();
let result = with_context__handler(&matches, &ctx);
assert!(result.is_ok());
assert_eq!(result.unwrap(), 0);
}
#[handler]
fn with_matches(#[matches] m: &ArgMatches) -> Result<bool, anyhow::Error> {
Ok(m.get_flag("verbose"))
}
#[test]
fn test_with_matches() {
let matches = clap::Command::new("test")
.arg(
clap::Arg::new("verbose")
.short('v')
.action(clap::ArgAction::SetTrue),
)
.get_matches_from(vec!["test", "-v"]);
let ctx = CommandContext::default();
let result = with_matches__handler(&matches, &ctx);
assert!(result.is_ok());
assert!(result.unwrap());
}
#[handler]
fn silent_handler(#[arg] path: String) -> Result<(), anyhow::Error> {
let _ = path;
Ok(())
}
#[test]
fn test_silent_handler() {
let matches = clap::Command::new("test")
.arg(clap::Arg::new("path").required(true))
.get_matches_from(vec!["test", "/tmp/foo"]);
let ctx = CommandContext::default();
let result = silent_handler__handler(&matches, &ctx);
assert!(result.is_ok());
assert!(matches!(result.unwrap(), Output::Silent));
}
#[test]
fn test_original_function_preserved() {
let result = simple_flag(true);
assert!(result.is_ok());
assert!(result.unwrap());
let result = required_arg("test".to_string());
assert!(result.is_ok());
assert_eq!(result.unwrap(), "Hello, test!");
}
#[handler]
fn mixed_params(
#[flag] verbose: bool,
#[ctx] ctx: &CommandContext,
#[arg] limit: Option<usize>,
) -> Result<String, anyhow::Error> {
Ok(format!(
"verbose={}, path_len={}, limit={:?}",
verbose,
ctx.command_path.len(),
limit
))
}
#[test]
fn test_mixed_params() {
let matches = clap::Command::new("test")
.arg(
clap::Arg::new("verbose")
.short('v')
.action(clap::ArgAction::SetTrue),
)
.arg(clap::Arg::new("limit").value_parser(clap::value_parser!(usize)))
.get_matches_from(vec!["test", "-v", "5"]);
let ctx = CommandContext::default();
let result = mixed_params__handler(&matches, &ctx);
assert!(result.is_ok());
assert_eq!(result.unwrap(), "verbose=true, path_len=0, limit=Some(5)");
}
use standout_dispatch::verify::{verify_handler_args, ArgKind, ExpectedArg};
#[test]
fn test_expected_args_generated_for_flag() {
let expected = simple_flag__expected_args();
assert_eq!(expected.len(), 1);
assert_eq!(expected[0].cli_name, "verbose");
assert_eq!(expected[0].rust_name, "verbose");
assert_eq!(expected[0].kind, ArgKind::Flag);
}
#[test]
fn test_expected_args_generated_for_custom_name_flag() {
let expected = flag_with_name__expected_args();
assert_eq!(expected.len(), 1);
assert_eq!(expected[0].cli_name, "show-all");
assert_eq!(expected[0].rust_name, "all");
assert_eq!(expected[0].kind, ArgKind::Flag);
}
#[test]
fn test_expected_args_generated_for_required_arg() {
let expected = required_arg__expected_args();
assert_eq!(expected.len(), 1);
assert_eq!(expected[0].cli_name, "name");
assert_eq!(expected[0].rust_name, "name");
assert_eq!(expected[0].kind, ArgKind::RequiredArg);
}
#[test]
fn test_expected_args_generated_for_optional_arg() {
let expected = optional_arg__expected_args();
assert_eq!(expected.len(), 1);
assert_eq!(expected[0].cli_name, "limit");
assert_eq!(expected[0].rust_name, "limit");
assert_eq!(expected[0].kind, ArgKind::OptionalArg);
}
#[test]
fn test_expected_args_generated_for_vec_arg() {
let expected = vec_arg__expected_args();
assert_eq!(expected.len(), 1);
assert_eq!(expected[0].cli_name, "tags");
assert_eq!(expected[0].rust_name, "tags");
assert_eq!(expected[0].kind, ArgKind::VecArg);
}
#[test]
fn test_expected_args_excludes_ctx_and_matches() {
let expected = mixed_params__expected_args();
assert_eq!(expected.len(), 2);
assert_eq!(expected[0].cli_name, "verbose");
assert_eq!(expected[0].kind, ArgKind::Flag);
assert_eq!(expected[1].cli_name, "limit");
assert_eq!(expected[1].kind, ArgKind::OptionalArg);
}
#[test]
fn test_verification_passes_for_matching_command() {
let command = clap::Command::new("test").arg(
clap::Arg::new("verbose")
.short('v')
.action(clap::ArgAction::SetTrue),
);
let expected = simple_flag__expected_args();
assert!(verify_handler_args(&command, "simple_flag", &expected).is_ok());
}
#[test]
fn test_verification_fails_for_missing_arg() {
let command = clap::Command::new("test");
let expected = simple_flag__expected_args();
let err = verify_handler_args(&command, "simple_flag", &expected).unwrap_err();
assert_eq!(err.handler_name, "simple_flag");
assert_eq!(err.mismatches.len(), 1);
let msg = err.to_string();
assert!(msg.contains("verbose"));
assert!(msg.contains("argument not defined"));
}
#[test]
fn test_verification_fails_for_wrong_action() {
let command = clap::Command::new("test").arg(
clap::Arg::new("verbose")
.long("verbose")
.action(clap::ArgAction::Set), );
let expected = simple_flag__expected_args();
let err = verify_handler_args(&command, "simple_flag", &expected).unwrap_err();
assert_eq!(err.mismatches.len(), 1);
let msg = err.to_string();
assert!(msg.contains("boolean flag"));
assert!(msg.contains("ArgAction::Set"));
assert!(msg.contains("SetTrue")); }
#[test]
fn test_verification_error_message_is_helpful() {
let command = clap::Command::new("list").arg(
clap::Arg::new("verbose")
.long("verbose")
.action(clap::ArgAction::Set),
);
let expected = vec![ExpectedArg::flag("verbose", "verbose")];
let err = verify_handler_args(&command, "list_handler", &expected).unwrap_err();
let msg = err.to_string();
assert!(msg.contains("Handler `list_handler`"));
assert!(msg.contains("command `list`"));
assert!(msg.contains("Handler expects: boolean flag"));
assert!(msg.contains("Command defines:"));
assert!(msg.contains("Fix:"));
assert!(msg.contains("ArgAction::SetTrue"));
}