diff --git a/bin/commands/inbox.rs b/bin/commands/inbox.rs index 8b6b1900..00a023f2 100644 --- a/bin/commands/inbox.rs +++ b/bin/commands/inbox.rs @@ -41,19 +41,22 @@ Other options "#, }; +#[derive(Debug, PartialEq)] pub struct Options { op: Operation, } +#[derive(Debug, PartialEq)] pub enum Operation { List { opts: ListOptions }, Other { args: Vec }, + Unknown { args: Vec }, } #[derive(PartialEq, Eq)] pub enum OperationName { List, - Other, + Unknown, } #[derive(Debug, Default, Clone, PartialEq, Eq)] @@ -133,7 +136,14 @@ impl Args for Options { Value(val) if op == OperationName::List => match val.to_string_lossy().as_ref() { "list" => op = OperationName::List, - _ => op = OperationName::Other, + _ => { + op = OperationName::Unknown; + // Only enable forwarding if it was not already disabled explicitly + forward = match forward { + Some(false) => Some(false), + _ => Some(true), + }; + } }, _ => { if op == OperationName::List { @@ -169,6 +179,7 @@ impl Args for Options { OperationName::List if !forward => Operation::List { opts: ListOptions { json, ..list_opts }, }, + OperationName::Unknown if !forward => Operation::Unknown { args }, _ => Operation::Other { args }, }; @@ -228,6 +239,9 @@ pub async fn run(options: Options, ctx: impl terminal::Context) -> anyhow::Resul Operation::Other { args } => { let _ = crate::terminal::run_rad(Some("inbox"), &args); } + Operation::Unknown { .. } => { + anyhow::bail!("unknown operation provided"); + } } Ok(()) @@ -235,149 +249,138 @@ pub async fn run(options: Options, ctx: impl terminal::Context) -> anyhow::Resul #[cfg(test)] mod cli { - use std::process::Command; + use radicle_cli::terminal::args::Error; + use radicle_cli::terminal::Args; - use assert_cmd::prelude::*; - - use predicates::prelude::*; - - mod assert { - use predicates::prelude::*; - use predicates::str::ContainsPredicate; - - pub fn is_tui() -> ContainsPredicate { - predicate::str::contains("Inappropriate ioctl for device") - } - - pub fn is_rad_manual() -> ContainsPredicate { - predicate::str::contains("rad-inbox") - } - - pub fn is_inbox_help() -> ContainsPredicate { - predicate::str::contains("Terminal interfaces for notifications") - } - } + use super::{ListOptions, Operation, Options}; #[test] - #[ignore = "requires binary"] - fn empty_operation() -> Result<(), Box> { - let mut cmd = Command::cargo_bin("rad-tui")?; + fn empty_operation_should_default_to_list_and_not_be_forwarded( + ) -> Result<(), Box> { + let expected_op = Operation::List { + opts: ListOptions::default(), + }; - cmd.arg("inbox"); - cmd.assert().failure().stdout(assert::is_tui()); + let args = vec![]; + let (actual, _) = Options::from_args(args)?; + assert_eq!(actual.op, expected_op); Ok(()) } #[test] - #[ignore] - fn empty_operation_is_not_forwarded() -> Result<(), Box> { - let mut cmd = Command::cargo_bin("rad-tui")?; + fn empty_operation_with_help_should_be_forwarded() -> Result<(), Box> { + let args = vec!["--help".into()]; + let expected_op = Operation::Other { args: args.clone() }; - cmd.arg("inbox"); - cmd.assert().failure().stdout(assert::is_tui()); + let (actual, _) = Options::from_args(args)?; + assert_eq!(actual.op, expected_op); Ok(()) } #[test] - #[ignore = "requires binary"] - fn empty_operation_with_help_is_forwarded() -> Result<(), Box> { - let mut cmd = Command::cargo_bin("rad-tui")?; - - cmd.args(["inbox", "--help"]); - cmd.assert().success().stdout(assert::is_rad_manual()); - - Ok(()) - } - - #[test] - #[ignore = "requires binary"] - fn empty_operation_with_help_is_not_forwarded() -> Result<(), Box> { - let mut cmd = Command::cargo_bin("rad-tui")?; - - cmd.args(["inbox", "--help", "--no-forward"]); - cmd.assert().success().stdout(assert::is_inbox_help()); - - Ok(()) - } - - #[test] - #[ignore = "requires binary"] - fn empty_operation_is_not_forwarded_explicitly() -> Result<(), Box> { - let mut cmd = Command::cargo_bin("rad-tui")?; - - cmd.args(["inbox", "--no-forward"]); - cmd.assert().failure().stdout(assert::is_tui()); - - Ok(()) - } - - #[test] - #[ignore = "requires binary"] - fn list_operation_is_not_forwarded() -> Result<(), Box> { - let mut cmd = Command::cargo_bin("rad-tui")?; - - cmd.args(["inbox", "list"]); - cmd.assert().failure().stdout(assert::is_tui()); - - Ok(()) - } - - #[test] - #[ignore = "requires binary"] - fn list_operation_is_not_forwarded_explicitly() -> Result<(), Box> { - let mut cmd = Command::cargo_bin("rad-tui")?; - - cmd.args(["inbox", "list", "--no-forward"]); - cmd.assert().failure().stdout(assert::is_tui()); - - Ok(()) - } - - #[test] - #[ignore = "requires binary"] - fn list_operation_with_help_is_forwarded() -> Result<(), Box> { - let mut cmd = Command::cargo_bin("rad-tui")?; - - cmd.args(["inbox", "list", "--help"]); - cmd.assert().success().stdout(assert::is_rad_manual()); - - Ok(()) - } - - #[test] - #[ignore = "requires binary"] - fn list_operation_with_help_is_not_forwarded() -> Result<(), Box> { - let mut cmd = Command::cargo_bin("rad-tui")?; - - cmd.args(["inbox", "list", "--help", "--no-forward"]); - cmd.assert().success().stdout(assert::is_inbox_help()); - - Ok(()) - } - - #[test] - #[ignore = "requires binary"] - fn list_operation_with_help_is_not_forwarded_reversed() -> Result<(), Box> + fn empty_operation_with_help_should_not_be_forwarded() -> Result<(), Box> { - let mut cmd = Command::cargo_bin("rad-tui")?; + let args = vec!["--help".into(), "--no-forward".into()]; - cmd.args(["inbox", "list", "--no-forward", "--help"]); - cmd.assert().success().stdout(assert::is_inbox_help()); + let actual = Options::from_args(args).unwrap_err().downcast::()?; + assert!(matches!(actual, Error::Help)); Ok(()) } #[test] - #[ignore = "requires binary"] - fn unknown_operation_show_is_forwarded() -> Result<(), Box> { - let mut cmd = Command::cargo_bin("rad-tui")?; + fn empty_operation_should_not_be_forwarded() -> Result<(), Box> { + let expected_op = Operation::List { + opts: ListOptions::default(), + }; - cmd.args(["inbox", "show"]); - cmd.assert() - .success() - .stdout(predicate::str::contains("a Notification ID must be given")); + let args = vec!["--no-forward".into()]; + let (actual, _) = Options::from_args(args)?; + assert_eq!(actual.op, expected_op); + + Ok(()) + } + + #[test] + fn list_operation_should_not_be_forwarded() -> Result<(), Box> { + let expected_op = Operation::List { + opts: ListOptions::default(), + }; + + let args = vec!["list".into()]; + let (actual, _) = Options::from_args(args)?; + assert_eq!(actual.op, expected_op); + + Ok(()) + } + + #[test] + fn list_operation_should_not_be_forwarded_explicitly() -> Result<(), Box> + { + let expected_op = Operation::List { + opts: ListOptions::default(), + }; + + let args = vec!["list".into(), "--no-forward".into()]; + let (actual, _) = Options::from_args(args)?; + assert_eq!(actual.op, expected_op); + + Ok(()) + } + + #[test] + fn list_operation_with_help_should_be_forwarded() -> Result<(), Box> { + let args = vec!["list".into(), "--help".into()]; + let expected_op = Operation::Other { args: args.clone() }; + + let (actual, _) = Options::from_args(args)?; + assert_eq!(actual.op, expected_op); + + Ok(()) + } + + #[test] + fn list_operation_with_help_should_not_be_forwarded() -> Result<(), Box> + { + let args = vec!["list".into(), "--help".into(), "--no-forward".into()]; + let actual = Options::from_args(args).unwrap_err().downcast::()?; + + assert!(matches!(actual, Error::Help)); + + Ok(()) + } + + #[test] + fn list_operation_with_help_should_not_be_forwarded_reversed( + ) -> Result<(), Box> { + let args = vec!["list".into(), "--no-forward".into(), "--help".into()]; + let actual = Options::from_args(args).unwrap_err().downcast::()?; + + assert!(matches!(actual, Error::Help)); + + Ok(()) + } + + #[test] + fn unknown_operation_should_be_forwarded() -> Result<(), Box> { + let args = vec!["operation".into()]; + let expected_op = Operation::Other { args: args.clone() }; + + let (actual, _) = Options::from_args(args)?; + assert_eq!(actual.op, expected_op); + + Ok(()) + } + + #[test] + fn unknown_operation_should_not_be_forwarded() -> Result<(), Box> { + let args = vec!["operation".into(), "--no-forward".into()]; + let expected_op = Operation::Unknown { args: args.clone() }; + + let (actual, _) = Options::from_args(args)?; + assert_eq!(actual.op, expected_op); Ok(()) } diff --git a/bin/commands/issue.rs b/bin/commands/issue.rs index 34ac565b..6886100a 100644 --- a/bin/commands/issue.rs +++ b/bin/commands/issue.rs @@ -48,20 +48,23 @@ Other options "#, }; +#[derive(Debug, PartialEq)] pub struct Options { op: Operation, repo: Option, } +#[derive(Debug, PartialEq)] pub enum Operation { List { opts: ListOptions }, Other { args: Vec }, + Unknown { args: Vec }, } #[derive(PartialEq, Eq)] pub enum OperationName { List, - Other, + Unknown, } #[derive(Debug, Default, Clone, PartialEq, Eq)] @@ -145,7 +148,14 @@ impl Args for Options { Value(val) if op == OperationName::List => match val.to_string_lossy().as_ref() { "list" => op = OperationName::List, - _ => op = OperationName::Other, + _ => { + op = OperationName::Unknown; + // Only enable forwarding if it was not already disabled explicitly + forward = match forward { + Some(false) => Some(false), + _ => Some(true), + }; + } }, _ => { if op == OperationName::List { @@ -169,6 +179,7 @@ impl Args for Options { OperationName::List if !forward => Operation::List { opts: ListOptions { json, ..list_opts }, }, + OperationName::Unknown if !forward => Operation::Unknown { args }, _ => Operation::Other { args }, }; @@ -231,6 +242,9 @@ pub async fn run(options: Options, ctx: impl terminal::Context) -> anyhow::Resul Operation::Other { args } => { let _ = crate::terminal::run_rad(Some("issue"), &args); } + Operation::Unknown { .. } => { + anyhow::bail!("unknown operation provided"); + } } Ok(()) @@ -238,175 +252,138 @@ pub async fn run(options: Options, ctx: impl terminal::Context) -> anyhow::Resul #[cfg(test)] mod cli { - use std::process::Command; + use radicle_cli::terminal::args::Error; + use radicle_cli::terminal::Args; - use assert_cmd::prelude::*; - - use predicates::prelude::*; - - mod assert { - use predicates::prelude::*; - use predicates::str::ContainsPredicate; - - pub fn is_tui() -> ContainsPredicate { - predicate::str::contains("Inappropriate ioctl for device") - } - - pub fn is_rad_manual() -> ContainsPredicate { - predicate::str::contains("rad-issue") - } - - pub fn is_issue_help() -> ContainsPredicate { - predicate::str::contains("Terminal interfaces for issues") - } - } + use super::{ListOptions, Operation, Options}; #[test] - #[ignore = "requires binary"] - fn empty_operation() -> Result<(), Box> { - let mut cmd = Command::cargo_bin("rad-tui")?; + fn empty_operation_should_default_to_list_and_not_be_forwarded( + ) -> Result<(), Box> { + let expected_op = Operation::List { + opts: ListOptions::default(), + }; - cmd.arg("issue"); - cmd.assert().failure().stdout(assert::is_tui()); + let args = vec![]; + let (actual, _) = Options::from_args(args)?; + assert_eq!(actual.op, expected_op); Ok(()) } #[test] - #[ignore = "requires binary"] - fn empty_operation_is_not_forwarded() -> Result<(), Box> { - let mut cmd = Command::cargo_bin("rad-tui")?; + fn empty_operation_with_help_should_be_forwarded() -> Result<(), Box> { + let args = vec!["--help".into()]; + let expected_op = Operation::Other { args: args.clone() }; - cmd.arg("issue"); - cmd.assert().failure().stdout(assert::is_tui()); + let (actual, _) = Options::from_args(args)?; + assert_eq!(actual.op, expected_op); Ok(()) } #[test] - #[ignore = "requires binary"] - fn empty_operation_with_help_is_forwarded() -> Result<(), Box> { - let mut cmd = Command::cargo_bin("rad-tui")?; - - cmd.args(["issue", "--help"]); - cmd.assert().success().stdout(assert::is_rad_manual()); - - Ok(()) - } - - #[test] - #[ignore = "requires binary"] - fn empty_operation_with_help_is_not_forwarded() -> Result<(), Box> { - let mut cmd = Command::cargo_bin("rad-tui")?; - - cmd.args(["issue", "--help", "--no-forward"]); - cmd.assert().success().stdout(assert::is_issue_help()); - - Ok(()) - } - - #[test] - #[ignore = "requires binary"] - fn empty_operation_is_not_forwarded_explicitly() -> Result<(), Box> { - let mut cmd = Command::cargo_bin("rad-tui")?; - - cmd.args(["issue", "--no-forward"]); - cmd.assert().failure().stdout(assert::is_tui()); - - Ok(()) - } - - #[test] - #[ignore = "requires binary"] - fn list_operation_is_not_forwarded() -> Result<(), Box> { - let mut cmd = Command::cargo_bin("rad-tui")?; - - cmd.args(["issue", "list"]); - cmd.assert().failure().stdout(assert::is_tui()); - - Ok(()) - } - - #[test] - #[ignore = "requires binary"] - fn list_operation_is_not_forwarded_explicitly() -> Result<(), Box> { - let mut cmd = Command::cargo_bin("rad-tui")?; - - cmd.args(["issue", "list", "--no-forward"]); - cmd.assert().failure().stdout(assert::is_tui()); - - Ok(()) - } - - #[test] - #[ignore = "requires binary"] - fn list_operation_with_help_is_forwarded() -> Result<(), Box> { - let mut cmd = Command::cargo_bin("rad-tui")?; - - cmd.args(["issue", "list", "--help"]); - cmd.assert().success().stdout(assert::is_rad_manual()); - - Ok(()) - } - - #[test] - #[ignore = "requires binary"] - fn list_operation_with_help_is_not_forwarded() -> Result<(), Box> { - let mut cmd = Command::cargo_bin("rad-tui")?; - - cmd.args(["issue", "list", "--help", "--no-forward"]); - cmd.assert().success().stdout(assert::is_issue_help()); - - Ok(()) - } - - #[test] - #[ignore = "requires binary"] - fn list_operation_with_help_is_not_forwarded_reversed() -> Result<(), Box> + fn empty_operation_with_help_should_not_be_forwarded() -> Result<(), Box> { - let mut cmd = Command::cargo_bin("rad-tui")?; + let args = vec!["--help".into(), "--no-forward".into()]; - cmd.args(["issue", "list", "--no-forward", "--help"]); - cmd.assert().success().stdout(assert::is_issue_help()); + let actual = Options::from_args(args).unwrap_err().downcast::()?; + assert!(matches!(actual, Error::Help)); Ok(()) } #[test] - #[ignore = "requires binary"] - fn unknown_operation_show_is_forwarded() -> Result<(), Box> { - let mut cmd = Command::cargo_bin("rad-tui")?; + fn empty_operation_should_not_be_forwarded() -> Result<(), Box> { + let expected_op = Operation::List { + opts: ListOptions::default(), + }; - cmd.args(["issue", "show"]); - cmd.assert().success().stdout(predicate::str::contains( - "Error: rad issue: an issue must be provided", - )); + let args = vec!["--no-forward".into()]; + let (actual, _) = Options::from_args(args)?; + assert_eq!(actual.op, expected_op); Ok(()) } #[test] - #[ignore = "requires binary"] - fn unknown_operation_edit_is_forwarded() -> Result<(), Box> { - let mut cmd = Command::cargo_bin("rad-tui")?; + fn list_operation_should_not_be_forwarded() -> Result<(), Box> { + let expected_op = Operation::List { + opts: ListOptions::default(), + }; - cmd.args(["issue", "edit"]); - cmd.assert().success().stdout(predicate::str::contains( - "Error: rad issue: an issue must be provided", - )); + let args = vec!["list".into()]; + let (actual, _) = Options::from_args(args)?; + assert_eq!(actual.op, expected_op); Ok(()) } #[test] - #[ignore = "requires binary"] - fn unknown_operation_is_not_forwarded() -> Result<(), Box> { - let mut cmd = Command::cargo_bin("rad-tui")?; + fn list_operation_should_not_be_forwarded_explicitly() -> Result<(), Box> + { + let expected_op = Operation::List { + opts: ListOptions::default(), + }; - cmd.args(["issue", "operation", "--no-forward"]); - cmd.assert().success().stdout(predicate::str::contains( - "Error: rad issue: unknown operation", - )); + let args = vec!["list".into(), "--no-forward".into()]; + let (actual, _) = Options::from_args(args)?; + assert_eq!(actual.op, expected_op); + + Ok(()) + } + + #[test] + fn list_operation_with_help_should_be_forwarded() -> Result<(), Box> { + let args = vec!["list".into(), "--help".into()]; + let expected_op = Operation::Other { args: args.clone() }; + + let (actual, _) = Options::from_args(args)?; + assert_eq!(actual.op, expected_op); + + Ok(()) + } + + #[test] + fn list_operation_with_help_should_not_be_forwarded() -> Result<(), Box> + { + let args = vec!["list".into(), "--help".into(), "--no-forward".into()]; + let actual = Options::from_args(args).unwrap_err().downcast::()?; + + assert!(matches!(actual, Error::Help)); + + Ok(()) + } + + #[test] + fn list_operation_with_help_should_not_be_forwarded_reversed( + ) -> Result<(), Box> { + let args = vec!["list".into(), "--no-forward".into(), "--help".into()]; + let actual = Options::from_args(args).unwrap_err().downcast::()?; + + assert!(matches!(actual, Error::Help)); + + Ok(()) + } + + #[test] + fn unknown_operation_should_be_forwarded() -> Result<(), Box> { + let args = vec!["operation".into()]; + let expected_op = Operation::Other { args: args.clone() }; + + let (actual, _) = Options::from_args(args)?; + assert_eq!(actual.op, expected_op); + + Ok(()) + } + + #[test] + fn unknown_operation_should_not_be_forwarded() -> Result<(), Box> { + let args = vec!["operation".into(), "--no-forward".into()]; + let expected_op = Operation::Unknown { args: args.clone() }; + + let (actual, _) = Options::from_args(args)?; + assert_eq!(actual.op, expected_op); Ok(()) } diff --git a/bin/commands/patch.rs b/bin/commands/patch.rs index c5739bde..2b33bef6 100644 --- a/bin/commands/patch.rs +++ b/bin/commands/patch.rs @@ -55,14 +55,17 @@ Other options "#, }; +#[derive(Debug, PartialEq)] pub struct Options { op: Operation, repo: Option, } +#[derive(Debug, PartialEq)] pub enum Operation { List { opts: ListOptions }, Review { opts: ReviewOptions }, + Unknown { args: Vec }, Other { args: Vec }, } @@ -71,7 +74,7 @@ pub enum Operation { pub enum OperationName { List, Review, - Other, + Unknown, } #[derive(Debug, Default, Clone, PartialEq, Eq)] @@ -197,7 +200,14 @@ impl Args for Options { Value(val) if op == OperationName::List => match val.to_string_lossy().as_ref() { "list" => op = OperationName::List, "review" => op = OperationName::Review, - _ => op = OperationName::Other, + _ => { + op = OperationName::Unknown; + // Only enable forwarding if it was not already disabled explicitly + forward = match forward { + Some(false) => Some(false), + _ => Some(true), + }; + } }, Value(val) if patch_id.is_none() => { let val = string(&val); @@ -237,6 +247,7 @@ impl Args for Options { }, }, OperationName::List if !forward => Operation::List { opts: list_opts }, + OperationName::Unknown if !forward => Operation::Unknown { args }, _ => Operation::Other { args }, }; @@ -312,6 +323,9 @@ pub async fn run(options: Options, ctx: impl terminal::Context) -> anyhow::Resul Operation::Other { args } => { let _ = crate::terminal::run_rad(Some("patch"), &args); } + Operation::Unknown { .. } => { + anyhow::bail!("unknown operation provided"); + } } Ok(()) @@ -373,7 +387,6 @@ mod interface { let patch = patch::find(&profile, &repo, &patch_id)? .ok_or_else(|| anyhow!("Patch `{patch_id}` not found"))?; - let (_, revision) = opts.revision_or_latest(&patch, &repo)?; let hunks = ReviewBuilder::new(&repo).hunks(revision)?; @@ -485,175 +498,138 @@ mod interface { #[cfg(test)] mod cli { - use std::process::Command; + use radicle_cli::terminal::args::Error; + use radicle_cli::terminal::Args; - use assert_cmd::prelude::*; - - use predicates::prelude::*; - - mod assert { - use predicates::prelude::*; - use predicates::str::ContainsPredicate; - - pub fn is_tui() -> ContainsPredicate { - predicate::str::contains("Inappropriate ioctl for device") - } - - pub fn is_rad_manual() -> ContainsPredicate { - predicate::str::contains("RAD-PATCH(1)") - } - - pub fn is_patch_help() -> ContainsPredicate { - predicate::str::contains("Terminal interfaces for patches") - } - } + use super::{ListOptions, Operation, Options}; #[test] - #[ignore = "requires binary"] - fn empty_operation() -> Result<(), Box> { - let mut cmd = Command::cargo_bin("rad-tui")?; + fn empty_operation_should_default_to_list_and_not_be_forwarded( + ) -> Result<(), Box> { + let expected_op = Operation::List { + opts: ListOptions::default(), + }; - cmd.arg("patch"); - cmd.assert().failure().stdout(assert::is_tui()); + let args = vec![]; + let (actual, _) = Options::from_args(args)?; + assert_eq!(actual.op, expected_op); Ok(()) } #[test] - #[ignore = "requires binary"] - fn empty_operation_is_not_forwarded() -> Result<(), Box> { - let mut cmd = Command::cargo_bin("rad-tui")?; + fn empty_operation_with_help_should_be_forwarded() -> Result<(), Box> { + let args = vec!["--help".into()]; + let expected_op = Operation::Other { args: args.clone() }; - cmd.arg("patch"); - cmd.assert().failure().stdout(assert::is_tui()); + let (actual, _) = Options::from_args(args)?; + assert_eq!(actual.op, expected_op); Ok(()) } #[test] - #[ignore = "requires binary"] - fn empty_operation_with_help_is_forwarded() -> Result<(), Box> { - let mut cmd = Command::cargo_bin("rad-tui")?; - - cmd.args(["patch", "--help"]); - cmd.assert().success().stdout(assert::is_rad_manual()); - - Ok(()) - } - - #[test] - #[ignore = "requires binary"] - fn empty_operation_with_help_is_not_forwarded() -> Result<(), Box> { - let mut cmd = Command::cargo_bin("rad-tui")?; - - cmd.args(["patch", "--help", "--no-forward"]); - cmd.assert().success().stdout(assert::is_patch_help()); - - Ok(()) - } - - #[test] - #[ignore = "requires binary"] - fn empty_operation_is_not_forwarded_explicitly() -> Result<(), Box> { - let mut cmd = Command::cargo_bin("rad-tui")?; - - cmd.args(["patch", "--no-forward"]); - cmd.assert().failure().stdout(assert::is_tui()); - - Ok(()) - } - - #[test] - #[ignore = "requires binary"] - fn list_operation_is_not_forwarded() -> Result<(), Box> { - let mut cmd = Command::cargo_bin("rad-tui")?; - - cmd.args(["patch", "list"]); - cmd.assert().failure().stdout(assert::is_tui()); - - Ok(()) - } - - #[test] - #[ignore = "requires binary"] - fn list_operation_is_not_forwarded_explicitly() -> Result<(), Box> { - let mut cmd = Command::cargo_bin("rad-tui")?; - - cmd.args(["patch", "list", "--no-forward"]); - cmd.assert().failure().stdout(assert::is_tui()); - - Ok(()) - } - - #[test] - #[ignore = "requires binary"] - fn list_operation_with_help_is_forwarded() -> Result<(), Box> { - let mut cmd = Command::cargo_bin("rad-tui")?; - - cmd.args(["patch", "list", "--help"]); - cmd.assert().success().stdout(assert::is_rad_manual()); - - Ok(()) - } - - #[test] - #[ignore = "requires binary"] - fn list_operation_with_help_is_not_forwarded() -> Result<(), Box> { - let mut cmd = Command::cargo_bin("rad-tui")?; - - cmd.args(["patch", "list", "--help", "--no-forward"]); - cmd.assert().success().stdout(assert::is_patch_help()); - - Ok(()) - } - - #[test] - #[ignore = "requires binary"] - fn list_operation_with_help_is_not_forwarded_reversed() -> Result<(), Box> + fn empty_operation_with_help_should_not_be_forwarded() -> Result<(), Box> { - let mut cmd = Command::cargo_bin("rad-tui")?; + let args = vec!["--help".into(), "--no-forward".into()]; - cmd.args(["patch", "list", "--no-forward", "--help"]); - cmd.assert().success().stdout(assert::is_patch_help()); + let actual = Options::from_args(args).unwrap_err().downcast::()?; + assert!(matches!(actual, Error::Help)); Ok(()) } #[test] - #[ignore = "requires binary"] - fn unknown_operation_show_is_forwarded() -> Result<(), Box> { - let mut cmd = Command::cargo_bin("rad-tui")?; + fn empty_operation_should_not_be_forwarded() -> Result<(), Box> { + let expected_op = Operation::List { + opts: ListOptions::default(), + }; - cmd.args(["patch", "show"]); - cmd.assert().success().stdout(predicate::str::contains( - "Error: rad patch: a patch must be provided", - )); + let args = vec!["--no-forward".into()]; + let (actual, _) = Options::from_args(args)?; + assert_eq!(actual.op, expected_op); Ok(()) } #[test] - #[ignore = "requires binary"] - fn unknown_operation_edit_is_forwarded() -> Result<(), Box> { - let mut cmd = Command::cargo_bin("rad-tui")?; + fn list_operation_should_not_be_forwarded() -> Result<(), Box> { + let expected_op = Operation::List { + opts: ListOptions::default(), + }; - cmd.args(["patch", "edit"]); - cmd.assert().success().stdout(predicate::str::contains( - "Error: rad patch: a patch must be provided", - )); + let args = vec!["list".into()]; + let (actual, _) = Options::from_args(args)?; + assert_eq!(actual.op, expected_op); Ok(()) } #[test] - #[ignore = "requires binary"] - fn unknown_operation_is_not_forwarded() -> Result<(), Box> { - let mut cmd = Command::cargo_bin("rad-tui")?; + fn list_operation_should_not_be_forwarded_explicitly() -> Result<(), Box> + { + let expected_op = Operation::List { + opts: ListOptions::default(), + }; - cmd.args(["patch", "operation", "--no-forward"]); - cmd.assert().success().stdout(predicate::str::contains( - "Error: rad patch: unknown operation", - )); + let args = vec!["list".into(), "--no-forward".into()]; + let (actual, _) = Options::from_args(args)?; + assert_eq!(actual.op, expected_op); + + Ok(()) + } + + #[test] + fn list_operation_with_help_should_be_forwarded() -> Result<(), Box> { + let args = vec!["list".into(), "--help".into()]; + let expected_op = Operation::Other { args: args.clone() }; + + let (actual, _) = Options::from_args(args)?; + assert_eq!(actual.op, expected_op); + + Ok(()) + } + + #[test] + fn list_operation_with_help_should_not_be_forwarded() -> Result<(), Box> + { + let args = vec!["list".into(), "--help".into(), "--no-forward".into()]; + let actual = Options::from_args(args).unwrap_err().downcast::()?; + + assert!(matches!(actual, Error::Help)); + + Ok(()) + } + + #[test] + fn list_operation_with_help_should_not_be_forwarded_reversed( + ) -> Result<(), Box> { + let args = vec!["list".into(), "--no-forward".into(), "--help".into()]; + let actual = Options::from_args(args).unwrap_err().downcast::()?; + + assert!(matches!(actual, Error::Help)); + + Ok(()) + } + + #[test] + fn unknown_operation_should_be_forwarded() -> Result<(), Box> { + let args = vec!["operation".into()]; + let expected_op = Operation::Other { args: args.clone() }; + + let (actual, _) = Options::from_args(args)?; + assert_eq!(actual.op, expected_op); + + Ok(()) + } + + #[test] + fn unknown_operation_should_not_be_forwarded() -> Result<(), Box> { + let args = vec!["operation".into(), "--no-forward".into()]; + let expected_op = Operation::Unknown { args: args.clone() }; + + let (actual, _) = Options::from_args(args)?; + assert_eq!(actual.op, expected_op); Ok(()) } diff --git a/bin/main.rs b/bin/main.rs index 79048c32..8d3011e4 100644 --- a/bin/main.rs +++ b/bin/main.rs @@ -9,6 +9,7 @@ mod terminal; mod test; mod ui; +use std::env::args_os; use std::ffi::OsString; use std::io; use std::{iter, process}; @@ -55,13 +56,13 @@ enum CommandName { Version, } -#[derive(Debug)] +#[derive(Default, Debug, PartialEq)] struct OtherOptions { args: Vec, forward: bool, } -#[derive(Debug)] +#[derive(Debug, PartialEq)] enum Command { Other { opts: OtherOptions }, Help, @@ -69,7 +70,9 @@ enum Command { } fn main() { - match parse_args().and_then(run) { + let args = args_os().collect::>(); + + match parse_args(&args[1..]).and_then(run) { Ok(_) => process::exit(0), Err(err) => { match err { @@ -83,10 +86,10 @@ fn main() { } } -fn parse_args() -> anyhow::Result { +fn parse_args(args: &[OsString]) -> anyhow::Result { use lexopt::prelude::*; - let mut parser = lexopt::Parser::from_env(); + let mut parser = lexopt::Parser::from_args(args); let mut command = None; let mut forward = true; let mut json = false; @@ -227,115 +230,97 @@ fn run_other(command: Option<&str>, args: &[OsString]) -> Result<(), Error> { #[cfg(test)] mod cli { - use assert_cmd::prelude::*; - use predicates::prelude::*; - use std::process::Command; - - mod assert { - use predicates::prelude::*; - use predicates::str::ContainsPredicate; - - pub fn is_rad_manual() -> ContainsPredicate { - predicate::str::contains("Radicle CLI Manual") - } - - pub fn is_rad_help() -> ContainsPredicate { - predicate::str::contains("Radicle command line interface") - } - - pub fn is_help() -> ContainsPredicate { - predicate::str::contains("Radicle terminal interfaces") - } - } + use crate::{parse_args, OtherOptions}; #[test] - #[ignore = "requires binary"] - fn can_be_executed() -> Result<(), Box> { - let mut cmd = Command::cargo_bin("rad-tui")?; + fn empty_command_should_be_forwarded() -> Result<(), Box> { + let args = vec![]; + let expected = super::Command::Other { + opts: OtherOptions { + args: args.clone(), + forward: true, + }, + }; - cmd.assert().success(); + let actual = parse_args(&args)?; + assert_eq!(actual, expected); Ok(()) } #[test] - #[ignore = "requires binary"] - fn empty_command_is_forwarded() -> Result<(), Box> { - let mut cmd = Command::cargo_bin("rad-tui")?; + fn empty_command_should_not_be_forwarded() -> Result<(), Box> { + let args = vec!["--no-forward".into()]; + let expected = super::Command::Other { + opts: OtherOptions::default(), + }; - cmd.assert().success().stdout(assert::is_rad_help()); + let actual = parse_args(&args)?; + assert_eq!(actual, expected); Ok(()) } #[test] - #[ignore = "requires binary"] - fn empty_command_is_not_forwarded() -> Result<(), Box> { - let mut cmd = Command::cargo_bin("rad-tui")?; + fn version_command_should_be_forwarded() -> Result<(), Box> { + let args = vec!["version".into()]; + let expected = super::Command::Other { + opts: OtherOptions { + args: args.clone(), + forward: true, + }, + }; - cmd.arg("--no-forward"); - cmd.assert().success().stdout(assert::is_help()); + let actual = parse_args(&args)?; + assert_eq!(actual, expected); Ok(()) } #[test] - #[ignore = "requires binary"] - fn version_command_is_forwarded() -> Result<(), Box> { - let mut cmd = Command::cargo_bin("rad-tui")?; + fn version_command_should_not_be_forwarded() -> Result<(), Box> { + let args = vec!["version".into(), "--no-forward".into()]; + let expected = super::Command::Version { json: false }; - cmd.arg("version"); - cmd.assert() - .success() - .stdout(predicate::str::starts_with("rad ")); + let actual = parse_args(&args)?; + assert_eq!(actual, expected); Ok(()) } #[test] - #[ignore = "requires binary"] - fn version_command_is_not_forwarded() -> Result<(), Box> { - let mut cmd = Command::cargo_bin("rad-tui")?; + fn version_command_should_print_json() -> Result<(), Box> { + let args = vec!["version".into(), "--no-forward".into(), "--json".into()]; + let expected = super::Command::Version { json: true }; - cmd.arg("version").arg("--no-forward"); - cmd.assert() - .success() - .stdout(predicate::str::starts_with("rad-tui ")); + let actual = parse_args(&args)?; + assert_eq!(actual, expected); Ok(()) } #[test] - #[ignore = "requires binary"] - fn version_command_prints_json() -> Result<(), Box> { - let mut cmd = Command::cargo_bin("rad-tui")?; + fn help_command_should_be_forwarded() -> Result<(), Box> { + let args = vec!["help".into()]; + let expected = super::Command::Other { + opts: OtherOptions { + args: args.clone(), + forward: true, + }, + }; - cmd.arg("version").arg("--no-forward").arg("--json"); - cmd.assert() - .success() - .stdout(predicate::str::contains("\"name\":\"rad-tui\"")); + let actual = parse_args(&args)?; + assert_eq!(actual, expected); Ok(()) } #[test] - #[ignore = "requires binary"] - fn help_command_is_forwarded() -> Result<(), Box> { - let mut cmd = Command::cargo_bin("rad-tui")?; + fn help_command_should_not_be_forwarded() -> Result<(), Box> { + let args = vec!["help".into(), "--no-forward".into()]; - cmd.arg("help"); - cmd.assert().success().stdout(assert::is_rad_manual()); - - Ok(()) - } - - #[test] - #[ignore = "requires binary"] - fn help_command_is_not_forwarded() -> Result<(), Box> { - let mut cmd = Command::cargo_bin("rad-tui")?; - - cmd.arg("help").arg("--no-forward"); - cmd.assert().success().stdout(assert::is_help()); + let actual = parse_args(&args)?; + assert!(matches!(actual, super::Command::Help)); Ok(()) }