cli: Test clone failure for unknown repo

This commit is contained in:
Alexis Sellier 2023-02-08 21:27:57 +01:00
parent 69ac8be8fd
commit b108342fab
No known key found for this signature in database
7 changed files with 69 additions and 13 deletions

View File

@ -0,0 +1,7 @@
Trying to clone a repository that is not in our routing table returns an error:
```
$ rad clone rad:zVNuptPuk5XauitpCWSNVCXGGfXW
ok Tracking relationship established for rad:zVNuptPuk5XauitpCWSNVCXGGfXW
== Clone failed no seeds found for rad:zVNuptPuk5XauitpCWSNVCXGGfXW
```

View File

@ -3,7 +3,7 @@ To create your first radicle project, navigate to a git repository, and run
the `init` command: the `init` command:
``` ```
$ rad init --name heartwood --description "Radicle Heartwood Protocol & Stack" --no-confirm $ rad init --name heartwood --description "Radicle Heartwood Protocol & Stack" --no-confirm --no-sync
Initializing local 🌱 project in . Initializing local 🌱 project in .

View File

@ -131,6 +131,8 @@ pub enum CloneError {
Payload(#[from] doc::PayloadError), Payload(#[from] doc::PayloadError),
#[error("project error: {0}")] #[error("project error: {0}")]
Project(#[from] ProjectError), Project(#[from] ProjectError),
#[error("no seeds found for {0}")]
NotFound(Id),
} }
pub fn clone<G: Signer>( pub fn clone<G: Signer>(
@ -149,8 +151,11 @@ pub fn clone<G: Signer>(
); );
} }
// Get seeds. // Get seeds. This consults the local routing table only.
let seeds = node.seeds(id)?; let seeds = node.seeds(id)?;
if seeds.is_empty() {
return Err(CloneError::NotFound(id));
}
// Fetch from all seeds. // Fetch from all seeds.
for seed in seeds { for seed in seeds {
let spinner = term::spinner(format!( let spinner = term::spinner(format!(

View File

@ -87,7 +87,7 @@ pub fn help(name: &str, version: &str, description: &str, usage: &str) {
} }
pub fn usage(name: &str, usage: &str) { pub fn usage(name: &str, usage: &str) {
eprintln!( println!(
"{} {}\n{}", "{} {}\n{}",
style("==").red(), style("==").red(),
style(format!("Error: rad-{name}: invalid usage")).red(), style(format!("Error: rad-{name}: invalid usage")).red(),
@ -95,8 +95,8 @@ pub fn usage(name: &str, usage: &str) {
); );
} }
pub fn eprintln(prefix: impl fmt::Display, msg: impl fmt::Display) { pub fn println(prefix: impl fmt::Display, msg: impl fmt::Display) {
eprintln!("{prefix} {msg}"); println!("{prefix} {msg}");
} }
pub fn indented(msg: impl fmt::Display) { pub fn indented(msg: impl fmt::Display) {
@ -108,7 +108,7 @@ pub fn subcommand(msg: impl fmt::Display) {
} }
pub fn warning(warning: &str) { pub fn warning(warning: &str) {
eprintln!( println!(
"{} {} {}", "{} {} {}",
style("**").yellow(), style("**").yellow(),
style("Warning:").yellow().bold(), style("Warning:").yellow().bold(),
@ -117,7 +117,7 @@ pub fn warning(warning: &str) {
} }
pub fn error(error: impl fmt::Display) { pub fn error(error: impl fmt::Display) {
eprintln!("{} {}", style("==").red(), style(error).red()); println!("{} {}", style("==").red(), style(error).red());
} }
pub fn fail(header: &str, error: &anyhow::Error) { pub fn fail(header: &str, error: &anyhow::Error) {
@ -129,7 +129,7 @@ pub fn fail(header: &str, error: &anyhow::Error) {
" " " "
}; };
eprintln!( println!(
"{} {}{}{}", "{} {}{}{}",
style("==").red(), style("==").red(),
style(header).red().reverse(), style(header).red().reverse(),
@ -139,7 +139,7 @@ pub fn fail(header: &str, error: &anyhow::Error) {
let cause = error.root_cause(); let cause = error.root_cause();
if cause.to_string() != error.to_string() { if cause.to_string() != error.to_string() {
eprintln!( println!(
"{} {}", "{} {}",
style("==").red().dim(), style("==").red().dim(),
style(error.root_cause()).red().dim() style(error.root_cause()).red().dim()
@ -148,7 +148,7 @@ pub fn fail(header: &str, error: &anyhow::Error) {
} }
if let Some(Error::WithHint { hint, .. }) = error.downcast_ref::<Error>() { if let Some(Error::WithHint { hint, .. }) = error.downcast_ref::<Error>() {
eprintln!("{} {}", style("==").yellow(), style(hint).yellow(),); println!("{} {}", style("==").yellow(), style(hint).yellow(),);
blank(); blank();
} }
} }

View File

@ -35,7 +35,7 @@ impl Spinner {
pub fn error(mut self, msg: impl ToString) { pub fn error(mut self, msg: impl ToString) {
let msg = msg.to_string(); let msg = msg.to_string();
self.message = format!("{} (error: {})", self.message, msg); self.message = format!("{} error: {}", self.message, msg);
self.set_failed(); self.set_failed();
} }
@ -52,7 +52,7 @@ impl Spinner {
pub fn set_failed(&mut self) { pub fn set_failed(&mut self) {
self.progress.finish_and_clear(); self.progress.finish_and_clear();
term::eprintln(style("!!").red().reverse(), &self.message); term::println(style("!!").red().reverse(), &self.message);
} }
} }

View File

@ -173,6 +173,25 @@ fn rad_clone() {
test("examples/rad-clone.md", working, Some(&bob.home), []).unwrap(); test("examples/rad-clone.md", working, Some(&bob.home), []).unwrap();
} }
#[test]
fn rad_clone_unknown() {
logger::init(log::Level::Debug);
let mut environment = Environment::new();
let alice = environment.node("alice");
let working = environment.tmp().join("working");
let alice = alice.spawn(Config::default());
test(
"examples/rad-clone-unknown.md",
working,
Some(&alice.home),
[],
)
.unwrap();
}
#[test] #[test]
fn rad_init_announce_refs() { fn rad_init_announce_refs() {
logger::init(log::Level::Debug); logger::init(log::Level::Debug);

View File

@ -8,6 +8,9 @@ use snapbox::cmd::{Command, OutputAssert};
use snapbox::{Assert, Substitutions}; use snapbox::{Assert, Substitutions};
use thiserror::Error; use thiserror::Error;
/// Error lines in the CLI are prefixed with this string.
const ERROR_PREFIX: &str = "==";
#[derive(Error, Debug)] #[derive(Error, Debug)]
pub enum Error { pub enum Error {
#[error("parsing failed")] #[error("parsing failed")]
@ -18,6 +21,12 @@ pub enum Error {
Snapbox(#[from] snapbox::Error), Snapbox(#[from] snapbox::Error),
} }
#[derive(Debug, PartialEq, Eq)]
enum ExitStatus {
Success,
Failure,
}
/// A test which may contain multiple assertions. /// A test which may contain multiple assertions.
#[derive(Debug, Default, PartialEq, Eq)] #[derive(Debug, Default, PartialEq, Eq)]
pub struct Test { pub struct Test {
@ -36,6 +45,8 @@ pub struct Assertion {
args: Vec<String>, args: Vec<String>,
/// Expected output (stdout or stderr). /// Expected output (stdout or stderr).
expected: String, expected: String,
/// Expected exit status.
exit: ExitStatus,
} }
#[derive(Debug, Default, PartialEq, Eq)] #[derive(Debug, Default, PartialEq, Eq)]
@ -112,8 +123,12 @@ impl TestFormula {
command: cmd.to_owned(), command: cmd.to_owned(),
args: args.to_owned(), args: args.to_owned(),
expected: String::new(), expected: String::new(),
exit: ExitStatus::Success,
}); });
} else if let Some(test) = test.assertions.last_mut() { } else if let Some(test) = test.assertions.last_mut() {
if line.starts_with(ERROR_PREFIX) {
test.exit = ExitStatus::Failure;
}
test.expected.push_str(line.as_str()); test.expected.push_str(line.as_str());
test.expected.push('\n'); test.expected.push('\n');
} else { } else {
@ -181,7 +196,14 @@ impl TestFormula {
match result { match result {
Ok(output) => { Ok(output) => {
let assert = OutputAssert::new(output).with_assert(assert.clone()); let assert = OutputAssert::new(output).with_assert(assert.clone());
assert.stdout_matches(&assertion.expected).success(); match assertion.exit {
ExitStatus::Success => {
assert.stdout_matches(&assertion.expected).success();
}
ExitStatus::Failure => {
assert.stdout_matches(&assertion.expected).failure();
}
}
} }
Err(err) => { Err(err) => {
if err.kind() == io::ErrorKind::NotFound { if err.kind() == io::ErrorKind::NotFound {
@ -246,6 +268,7 @@ $ rad sync
expected: String::from( expected: String::from(
"Tracking relationship established for @dave.\nNothing to do.\n\n", "Tracking relationship established for @dave.\nNothing to do.\n\n",
), ),
exit: ExitStatus::Success,
}, },
Assertion { Assertion {
command: String::from("rad"), command: String::from("rad"),
@ -253,6 +276,7 @@ $ rad sync
expected: String::from( expected: String::from(
"Tracking relationship established for @sean.\nNothing to do.\n", "Tracking relationship established for @sean.\nNothing to do.\n",
), ),
exit: ExitStatus::Success,
}, },
], ],
}, },
@ -262,6 +286,7 @@ $ rad sync
command: String::from("rad"), command: String::from("rad"),
args: vec![String::from("sync")], args: vec![String::from("sync")],
expected: String::new(), expected: String::new(),
exit: ExitStatus::Success,
}], }],
}, },
], ],