From cab0fe087558d5d424496e148d98035520d892fa Mon Sep 17 00:00:00 2001 From: Chip Senkbeil Date: Sun, 29 Aug 2021 18:33:25 -0500 Subject: [PATCH] Add tests for cli dir-read, dir-create, remove, and copy --- core/src/server/distant/handler.rs | 14 + tests/cli/action/copy.rs | 198 ++++++++++ tests/cli/action/dir_create.rs | 162 +++++++++ tests/cli/action/dir_read.rs | 520 +++++++++++++++++++++++++++ tests/cli/action/file_append.rs | 44 +-- tests/cli/action/file_append_text.rs | 44 +-- tests/cli/action/file_read.rs | 28 +- tests/cli/action/file_read_text.rs | 28 +- tests/cli/action/file_write.rs | 34 +- tests/cli/action/file_write_text.rs | 44 +-- tests/cli/action/mod.rs | 4 + tests/cli/action/remove.rs | 229 ++++++++++++ 12 files changed, 1238 insertions(+), 111 deletions(-) create mode 100644 tests/cli/action/copy.rs create mode 100644 tests/cli/action/dir_create.rs create mode 100644 tests/cli/action/dir_read.rs create mode 100644 tests/cli/action/remove.rs diff --git a/core/src/server/distant/handler.rs b/core/src/server/distant/handler.rs index d7a3d69..2dad26e 100644 --- a/core/src/server/distant/handler.rs +++ b/core/src/server/distant/handler.rs @@ -265,6 +265,10 @@ async fn remove(path: PathBuf, force: bool) -> Result async fn copy(src: PathBuf, dst: PathBuf) -> Result { let src_metadata = tokio::fs::metadata(src.as_path()).await?; if src_metadata.is_dir() { + // Create the destination directory first, regardless of if anything + // is in the source directory + tokio::fs::create_dir_all(dst.as_path()).await?; + for entry in WalkDir::new(src.as_path()) .min_depth(1) .follow_links(false) @@ -1316,6 +1320,16 @@ mod tests { todo!(); } + #[tokio::test] + async fn copy_should_support_copying_an_empty_directory() { + todo!(); + } + + #[tokio::test] + async fn copy_should_support_copying_a_directory_that_only_contains_directories() { + todo!(); + } + #[tokio::test] async fn copy_should_support_copying_a_single_file() { todo!(); diff --git a/tests/cli/action/copy.rs b/tests/cli/action/copy.rs new file mode 100644 index 0000000..6aff037 --- /dev/null +++ b/tests/cli/action/copy.rs @@ -0,0 +1,198 @@ +use crate::cli::{ + fixtures::*, + utils::{random_tenant, FAILURE_LINE}, +}; +use assert_cmd::Command; +use assert_fs::prelude::*; +use distant::ExitCode; +use distant_core::{ + data::{Error, ErrorKind}, + Request, RequestData, Response, ResponseData, +}; +use predicates::prelude::*; +use rstest::*; + +const FILE_CONTENTS: &str = r#" +some text +on multiple lines +that is a file's contents +"#; + +#[rstest] +fn should_support_copying_file(mut action_cmd: Command) { + let temp = assert_fs::TempDir::new().unwrap(); + + let src = temp.child("file"); + src.write_str(FILE_CONTENTS).unwrap(); + + let dst = temp.child("file2"); + + // distant action copy {src} {dst} + action_cmd + .args(&["copy", src.to_str().unwrap(), dst.to_str().unwrap()]) + .assert() + .success() + .stdout("") + .stderr(""); + + src.assert(predicate::path::exists()); + dst.assert(predicate::path::eq_file(src.path())); +} + +#[rstest] +fn should_support_copying_nonempty_directory(mut action_cmd: Command) { + let temp = assert_fs::TempDir::new().unwrap(); + + // Make a non-empty directory + let src = temp.child("dir"); + src.create_dir_all().unwrap(); + let src_file = src.child("file"); + src_file.write_str(FILE_CONTENTS).unwrap(); + + let dst = temp.child("dir2"); + let dst_file = dst.child("file"); + + // distant action copy {src} {dst} + action_cmd + .args(&["copy", src.to_str().unwrap(), dst.to_str().unwrap()]) + .assert() + .success() + .stdout("") + .stderr(""); + + src_file.assert(predicate::path::exists()); + dst_file.assert(predicate::path::eq_file(src_file.path())); +} + +#[rstest] +fn yield_an_error_when_fails(mut action_cmd: Command) { + let temp = assert_fs::TempDir::new().unwrap(); + + let src = temp.child("dir"); + let dst = temp.child("dir2"); + + // distant action copy {src} {dst} + action_cmd + .args(&["copy", src.to_str().unwrap(), dst.to_str().unwrap()]) + .assert() + .code(ExitCode::Software.to_i32()) + .stdout("") + .stderr(FAILURE_LINE.clone()); + + src.assert(predicate::path::missing()); + dst.assert(predicate::path::missing()); +} + +#[rstest] +fn should_support_json_copying_file(mut action_cmd: Command) { + let temp = assert_fs::TempDir::new().unwrap(); + + let src = temp.child("file"); + src.write_str(FILE_CONTENTS).unwrap(); + + let dst = temp.child("file2"); + + let req = Request { + id: rand::random(), + tenant: random_tenant(), + payload: vec![RequestData::Copy { + src: src.to_path_buf(), + dst: dst.to_path_buf(), + }], + }; + + // distant action --format json --interactive + let cmd = action_cmd + .args(&["--format", "json"]) + .arg("--interactive") + .write_stdin(format!("{}\n", serde_json::to_string(&req).unwrap())) + .assert() + .success() + .stderr(""); + + let res: Response = serde_json::from_slice(&cmd.get_output().stdout).unwrap(); + assert_eq!(res.payload[0], ResponseData::Ok); + + src.assert(predicate::path::exists()); + dst.assert(predicate::path::eq_file(src.path())); +} + +#[rstest] +fn should_support_json_copying_nonempty_directory(mut action_cmd: Command) { + let temp = assert_fs::TempDir::new().unwrap(); + + // Make a non-empty directory + let src = temp.child("dir"); + src.create_dir_all().unwrap(); + let src_file = src.child("file"); + src_file.write_str(FILE_CONTENTS).unwrap(); + + let dst = temp.child("dir2"); + let dst_file = dst.child("file"); + + let req = Request { + id: rand::random(), + tenant: random_tenant(), + payload: vec![RequestData::Copy { + src: src.to_path_buf(), + dst: dst.to_path_buf(), + }], + }; + + // distant action --format json --interactive + let cmd = action_cmd + .args(&["--format", "json"]) + .arg("--interactive") + .write_stdin(format!("{}\n", serde_json::to_string(&req).unwrap())) + .assert() + .success() + .stderr(""); + + let res: Response = serde_json::from_slice(&cmd.get_output().stdout).unwrap(); + assert_eq!(res.payload[0], ResponseData::Ok); + + src_file.assert(predicate::path::exists()); + dst_file.assert(predicate::path::eq_file(src_file.path())); +} + +#[rstest] +fn should_support_json_output_for_error(mut action_cmd: Command) { + let temp = assert_fs::TempDir::new().unwrap(); + + let src = temp.child("dir"); + let dst = temp.child("dir2"); + + let req = Request { + id: rand::random(), + tenant: random_tenant(), + payload: vec![RequestData::Copy { + src: src.to_path_buf(), + dst: dst.to_path_buf(), + }], + }; + + // distant action --format json --interactive + let cmd = action_cmd + .args(&["--format", "json"]) + .arg("--interactive") + .write_stdin(format!("{}\n", serde_json::to_string(&req).unwrap())) + .assert() + .success() + .stderr(""); + + let res: Response = serde_json::from_slice(&cmd.get_output().stdout).unwrap(); + assert!( + matches!( + res.payload[0], + ResponseData::Error(Error { + kind: ErrorKind::NotFound, + .. + }) + ), + "Unexpected response: {:?}", + res.payload[0] + ); + + src.assert(predicate::path::missing()); + dst.assert(predicate::path::missing()); +} diff --git a/tests/cli/action/dir_create.rs b/tests/cli/action/dir_create.rs new file mode 100644 index 0000000..313e6a4 --- /dev/null +++ b/tests/cli/action/dir_create.rs @@ -0,0 +1,162 @@ +use crate::cli::{ + fixtures::*, + utils::{random_tenant, FAILURE_LINE}, +}; +use assert_cmd::Command; +use assert_fs::prelude::*; +use distant::ExitCode; +use distant_core::{ + data::{Error, ErrorKind}, + Request, RequestData, Response, ResponseData, +}; +use predicates::prelude::*; +use rstest::*; + +#[rstest] +fn should_report_ok_when_done(mut action_cmd: Command) { + let temp = assert_fs::TempDir::new().unwrap(); + let dir = temp.child("dir"); + + // distant action dir-create {path} + action_cmd + .args(&["dir-create", dir.to_str().unwrap()]) + .assert() + .success() + .stdout("") + .stderr(""); + + dir.assert(predicate::path::exists()); + dir.assert(predicate::path::is_dir()); +} + +#[rstest] +fn should_support_creating_missing_parent_directories_if_specified(mut action_cmd: Command) { + let temp = assert_fs::TempDir::new().unwrap(); + let dir = temp.child("dir1").child("dir2"); + + // distant action dir-create {path} + action_cmd + .args(&["dir-create", "--all", dir.to_str().unwrap()]) + .assert() + .success() + .stdout("") + .stderr(""); + + dir.assert(predicate::path::exists()); + dir.assert(predicate::path::is_dir()); +} + +#[rstest] +fn yield_an_error_when_fails(mut action_cmd: Command) { + let temp = assert_fs::TempDir::new().unwrap(); + let dir = temp.child("missing-dir").child("dir"); + + // distant action dir-create {path} + action_cmd + .args(&["dir-create", dir.to_str().unwrap()]) + .assert() + .code(ExitCode::Software.to_i32()) + .stdout("") + .stderr(FAILURE_LINE.clone()); + + dir.assert(predicate::path::missing()); +} + +#[rstest] +fn should_support_json_output(mut action_cmd: Command) { + let temp = assert_fs::TempDir::new().unwrap(); + let dir = temp.child("dir"); + + let req = Request { + id: rand::random(), + tenant: random_tenant(), + payload: vec![RequestData::DirCreate { + path: dir.to_path_buf(), + all: false, + }], + }; + + // distant action --format json --interactive + let cmd = action_cmd + .args(&["--format", "json"]) + .arg("--interactive") + .write_stdin(format!("{}\n", serde_json::to_string(&req).unwrap())) + .assert() + .success() + .stderr(""); + + let res: Response = serde_json::from_slice(&cmd.get_output().stdout).unwrap(); + assert_eq!(res.payload[0], ResponseData::Ok); + + dir.assert(predicate::path::exists()); + dir.assert(predicate::path::is_dir()); +} + +#[rstest] +fn should_support_json_creating_missing_parent_directories_if_specified(mut action_cmd: Command) { + let temp = assert_fs::TempDir::new().unwrap(); + let dir = temp.child("dir1").child("dir2"); + + let req = Request { + id: rand::random(), + tenant: random_tenant(), + payload: vec![RequestData::DirCreate { + path: dir.to_path_buf(), + all: true, + }], + }; + + // distant action --format json --interactive + let cmd = action_cmd + .args(&["--format", "json"]) + .arg("--interactive") + .write_stdin(format!("{}\n", serde_json::to_string(&req).unwrap())) + .assert() + .success() + .stderr(""); + + let res: Response = serde_json::from_slice(&cmd.get_output().stdout).unwrap(); + assert_eq!(res.payload[0], ResponseData::Ok); + + dir.assert(predicate::path::exists()); + dir.assert(predicate::path::is_dir()); +} + +#[rstest] +fn should_support_json_output_for_error(mut action_cmd: Command) { + let temp = assert_fs::TempDir::new().unwrap(); + let dir = temp.child("missing-dir").child("dir"); + + let req = Request { + id: rand::random(), + tenant: random_tenant(), + payload: vec![RequestData::DirCreate { + path: dir.to_path_buf(), + all: false, + }], + }; + + // distant action --format json --interactive + let cmd = action_cmd + .args(&["--format", "json"]) + .arg("--interactive") + .write_stdin(format!("{}\n", serde_json::to_string(&req).unwrap())) + .assert() + .success() + .stderr(""); + + let res: Response = serde_json::from_slice(&cmd.get_output().stdout).unwrap(); + assert!( + matches!( + res.payload[0], + ResponseData::Error(Error { + kind: ErrorKind::NotFound, + .. + }) + ), + "Unexpected response: {:?}", + res.payload[0] + ); + + dir.assert(predicate::path::missing()); +} diff --git a/tests/cli/action/dir_read.rs b/tests/cli/action/dir_read.rs new file mode 100644 index 0000000..52bdcdf --- /dev/null +++ b/tests/cli/action/dir_read.rs @@ -0,0 +1,520 @@ +use crate::cli::{ + fixtures::*, + utils::{random_tenant, FAILURE_LINE}, +}; +use assert_cmd::Command; +use assert_fs::prelude::*; +use distant::ExitCode; +use distant_core::{ + data::{DirEntry, Error, ErrorKind, FileType}, + Request, RequestData, Response, ResponseData, +}; +use rstest::*; +use std::path::PathBuf; + +/// Creates a directory in the form +/// +/// $TEMP/ +/// $TEMP/dir1/ +/// $TEMP/dir1/dira/ +/// $TEMP/dir1/dirb/ +/// $TEMP/dir1/dirb/file1 +/// $TEMP/dir1/file1 +/// $TEMP/dir1/file2 +/// $TEMP/dir2/ +/// $TEMP/dir2/dira/ +/// $TEMP/dir2/dirb/ +/// $TEMP/dir2/dirb/file1 +/// $TEMP/dir2/file1 +/// $TEMP/dir2/file2 +/// $TEMP/file1 +/// $TEMP/file2 +fn make_directory() -> assert_fs::TempDir { + let temp = assert_fs::TempDir::new().unwrap(); + + // $TEMP/file1 + // $TEMP/file2 + temp.child("file1").touch().unwrap(); + temp.child("file2").touch().unwrap(); + + // $TEMP/dir1/ + // $TEMP/dir1/file1 + // $TEMP/dir1/file2 + let dir1 = temp.child("dir1"); + dir1.create_dir_all().unwrap(); + dir1.child("file1").touch().unwrap(); + dir1.child("file2").touch().unwrap(); + + // $TEMP/dir1/dira/ + let dir1_dira = dir1.child("dira"); + dir1_dira.create_dir_all().unwrap(); + + // $TEMP/dir1/dirb/ + // $TEMP/dir1/dirb/file1 + let dir1_dirb = dir1.child("dirb"); + dir1_dirb.create_dir_all().unwrap(); + dir1_dirb.child("file1").touch().unwrap(); + + // $TEMP/dir2/ + // $TEMP/dir2/file1 + // $TEMP/dir2/file2 + let dir2 = temp.child("dir2"); + dir2.create_dir_all().unwrap(); + dir2.child("file1").touch().unwrap(); + dir2.child("file2").touch().unwrap(); + + // $TEMP/dir2/dira/ + let dir2_dira = dir2.child("dira"); + dir2_dira.create_dir_all().unwrap(); + + // $TEMP/dir2/dirb/ + // $TEMP/dir2/dirb/file1 + let dir2_dirb = dir2.child("dirb"); + dir2_dirb.create_dir_all().unwrap(); + dir2_dirb.child("file1").touch().unwrap(); + + temp +} + +#[rstest] +fn should_print_immediate_files_and_directories_by_default(mut action_cmd: Command) { + let temp = make_directory(); + + // distant action dir-read {path} + action_cmd + .args(&["dir-read", temp.to_str().unwrap()]) + .assert() + .success() + .stdout(concat!("dir1/\n", "dir2/\n", "file1\n", "file2\n")) + .stderr(""); +} + +#[rstest] +fn should_use_absolute_paths_if_specified(mut action_cmd: Command) { + let temp = make_directory(); + + // NOTE: Our root path is always canonicalized, so the absolute path + // provided is our canonicalized root path prepended + let root_path = temp.to_path_buf().canonicalize().unwrap(); + + // distant action dir-read --absolute {path} + action_cmd + .args(&["dir-read", "--absolute", temp.to_str().unwrap()]) + .assert() + .success() + .stdout(format!( + "{}\n", + vec![ + format!("{}/{}", root_path.to_str().unwrap(), "dir1/"), + format!("{}/{}", root_path.to_str().unwrap(), "dir2/"), + format!("{}/{}", root_path.to_str().unwrap(), "file1"), + format!("{}/{}", root_path.to_str().unwrap(), "file2"), + ] + .join("\n") + )) + .stderr(""); +} + +#[rstest] +fn should_print_all_files_and_directories_if_depth_is_0(mut action_cmd: Command) { + let temp = make_directory(); + + // distant action dir-read --depth 0 {path} + action_cmd + .args(&["dir-read", "--depth", "0", temp.to_str().unwrap()]) + .assert() + .success() + .stdout(concat!( + "dir1/\n", + "dir1/dira/\n", + "dir1/dirb/\n", + "dir1/dirb/file1\n", + "dir1/file1\n", + "dir1/file2\n", + "dir2/\n", + "dir2/dira/\n", + "dir2/dirb/\n", + "dir2/dirb/file1\n", + "dir2/file1\n", + "dir2/file2\n", + "file1\n", + "file2\n", + )) + .stderr(""); +} + +#[rstest] +fn should_include_root_directory_if_specified(mut action_cmd: Command) { + let temp = make_directory(); + + // NOTE: Our root path is always canonicalized, so yielded entry + // is the canonicalized version + let root_path = temp.to_path_buf().canonicalize().unwrap(); + + // distant action dir-read --include-root {path} + action_cmd + .args(&["dir-read", "--include-root", temp.to_str().unwrap()]) + .assert() + .success() + .stdout(format!( + "{}/\n{}", + root_path.to_str().unwrap(), + concat!("dir1/\n", "dir2/\n", "file1\n", "file2\n") + )) + .stderr(""); +} + +#[rstest] +fn yield_an_error_when_fails(mut action_cmd: Command) { + let temp = make_directory(); + let dir = temp.child("missing-dir"); + + // distant action dir-read {path} + action_cmd + .args(&["dir-read", dir.to_str().unwrap()]) + .assert() + .code(ExitCode::Software.to_i32()) + .stdout("") + .stderr(FAILURE_LINE.clone()); +} + +#[rstest] +fn should_support_json_output(mut action_cmd: Command) { + let temp = make_directory(); + + let req = Request { + id: rand::random(), + tenant: random_tenant(), + payload: vec![RequestData::DirRead { + path: temp.to_path_buf(), + depth: 1, + absolute: false, + canonicalize: false, + include_root: false, + }], + }; + + // distant action --format json --interactive + let cmd = action_cmd + .args(&["--format", "json"]) + .arg("--interactive") + .write_stdin(format!("{}\n", serde_json::to_string(&req).unwrap())) + .assert() + .success() + .stderr(""); + + let res: Response = serde_json::from_slice(&cmd.get_output().stdout).unwrap(); + assert_eq!( + res.payload[0], + ResponseData::DirEntries { + entries: vec![ + DirEntry { + path: PathBuf::from("dir1"), + file_type: FileType::Dir, + depth: 1 + }, + DirEntry { + path: PathBuf::from("dir2"), + file_type: FileType::Dir, + depth: 1 + }, + DirEntry { + path: PathBuf::from("file1"), + file_type: FileType::File, + depth: 1 + }, + DirEntry { + path: PathBuf::from("file2"), + file_type: FileType::File, + depth: 1 + }, + ], + errors: Vec::new(), + } + ); +} + +#[rstest] +fn should_support_json_returning_absolute_paths_if_specified(mut action_cmd: Command) { + let temp = make_directory(); + + // NOTE: Our root path is always canonicalized, so the absolute path + // provided is our canonicalized root path prepended + let root_path = temp.to_path_buf().canonicalize().unwrap(); + + let req = Request { + id: rand::random(), + tenant: random_tenant(), + payload: vec![RequestData::DirRead { + path: temp.to_path_buf(), + depth: 1, + absolute: true, + canonicalize: false, + include_root: false, + }], + }; + + // distant action --format json --interactive + let cmd = action_cmd + .args(&["--format", "json"]) + .arg("--interactive") + .write_stdin(format!("{}\n", serde_json::to_string(&req).unwrap())) + .assert() + .success() + .stderr(""); + + let res: Response = serde_json::from_slice(&cmd.get_output().stdout).unwrap(); + assert_eq!( + res.payload[0], + ResponseData::DirEntries { + entries: vec![ + DirEntry { + path: root_path.join("dir1"), + file_type: FileType::Dir, + depth: 1 + }, + DirEntry { + path: root_path.join("dir2"), + file_type: FileType::Dir, + depth: 1 + }, + DirEntry { + path: root_path.join("file1"), + file_type: FileType::File, + depth: 1 + }, + DirEntry { + path: root_path.join("file2"), + file_type: FileType::File, + depth: 1 + }, + ], + errors: Vec::new(), + } + ); +} + +#[rstest] +fn should_support_json_returning_all_files_and_directories_if_depth_is_0(mut action_cmd: Command) { + let temp = make_directory(); + + let req = Request { + id: rand::random(), + tenant: random_tenant(), + payload: vec![RequestData::DirRead { + path: temp.to_path_buf(), + depth: 0, + absolute: false, + canonicalize: false, + include_root: false, + }], + }; + + // distant action --format json --interactive + let cmd = action_cmd + .args(&["--format", "json"]) + .arg("--interactive") + .write_stdin(format!("{}\n", serde_json::to_string(&req).unwrap())) + .assert() + .success() + .stderr(""); + + let res: Response = serde_json::from_slice(&cmd.get_output().stdout).unwrap(); + assert_eq!( + res.payload[0], + ResponseData::DirEntries { + /* "dir1/\n", + "dir1/dira/\n", + "dir1/dirb/\n", + "dir1/dirb/file1\n", + "dir1/file1\n", + "dir1/file2\n", + "dir2/\n", + "dir2/dira/\n", + "dir2/dirb/\n", + "dir2/dirb/file1\n", + "dir2/file1\n", + "dir2/file2\n", + "file1\n", + "file2\n", */ + entries: vec![ + DirEntry { + path: PathBuf::from("dir1"), + file_type: FileType::Dir, + depth: 1 + }, + DirEntry { + path: PathBuf::from("dir1").join("dira"), + file_type: FileType::Dir, + depth: 2 + }, + DirEntry { + path: PathBuf::from("dir1").join("dirb"), + file_type: FileType::Dir, + depth: 2 + }, + DirEntry { + path: PathBuf::from("dir1").join("dirb").join("file1"), + file_type: FileType::File, + depth: 3 + }, + DirEntry { + path: PathBuf::from("dir1").join("file1"), + file_type: FileType::File, + depth: 2 + }, + DirEntry { + path: PathBuf::from("dir1").join("file2"), + file_type: FileType::File, + depth: 2 + }, + DirEntry { + path: PathBuf::from("dir2"), + file_type: FileType::Dir, + depth: 1 + }, + DirEntry { + path: PathBuf::from("dir2").join("dira"), + file_type: FileType::Dir, + depth: 2 + }, + DirEntry { + path: PathBuf::from("dir2").join("dirb"), + file_type: FileType::Dir, + depth: 2 + }, + DirEntry { + path: PathBuf::from("dir2").join("dirb").join("file1"), + file_type: FileType::File, + depth: 3 + }, + DirEntry { + path: PathBuf::from("dir2").join("file1"), + file_type: FileType::File, + depth: 2 + }, + DirEntry { + path: PathBuf::from("dir2").join("file2"), + file_type: FileType::File, + depth: 2 + }, + DirEntry { + path: PathBuf::from("file1"), + file_type: FileType::File, + depth: 1 + }, + DirEntry { + path: PathBuf::from("file2"), + file_type: FileType::File, + depth: 1 + }, + ], + errors: Vec::new(), + } + ); +} + +#[rstest] +fn should_support_json_including_root_directory_if_specified(mut action_cmd: Command) { + let temp = make_directory(); + + // NOTE: Our root path is always canonicalized, so yielded entry + // is the canonicalized version + let root_path = temp.to_path_buf().canonicalize().unwrap(); + + let req = Request { + id: rand::random(), + tenant: random_tenant(), + payload: vec![RequestData::DirRead { + path: temp.to_path_buf(), + depth: 1, + absolute: false, + canonicalize: false, + include_root: true, + }], + }; + + // distant action --format json --interactive + let cmd = action_cmd + .args(&["--format", "json"]) + .arg("--interactive") + .write_stdin(format!("{}\n", serde_json::to_string(&req).unwrap())) + .assert() + .success() + .stderr(""); + + let res: Response = serde_json::from_slice(&cmd.get_output().stdout).unwrap(); + assert_eq!( + res.payload[0], + ResponseData::DirEntries { + entries: vec![ + DirEntry { + path: root_path.to_path_buf(), + file_type: FileType::Dir, + depth: 0 + }, + DirEntry { + path: PathBuf::from("dir1"), + file_type: FileType::Dir, + depth: 1 + }, + DirEntry { + path: PathBuf::from("dir2"), + file_type: FileType::Dir, + depth: 1 + }, + DirEntry { + path: PathBuf::from("file1"), + file_type: FileType::File, + depth: 1 + }, + DirEntry { + path: PathBuf::from("file2"), + file_type: FileType::File, + depth: 1 + }, + ], + errors: Vec::new(), + } + ); +} + +#[rstest] +fn should_support_json_output_for_error(mut action_cmd: Command) { + let temp = make_directory(); + let dir = temp.child("missing-dir"); + + let req = Request { + id: rand::random(), + tenant: random_tenant(), + payload: vec![RequestData::DirRead { + path: dir.to_path_buf(), + depth: 1, + absolute: false, + canonicalize: false, + include_root: false, + }], + }; + + // distant action --format json --interactive + let cmd = action_cmd + .args(&["--format", "json"]) + .arg("--interactive") + .write_stdin(format!("{}\n", serde_json::to_string(&req).unwrap())) + .assert() + .success() + .stderr(""); + + let res: Response = serde_json::from_slice(&cmd.get_output().stdout).unwrap(); + assert!( + matches!( + res.payload[0], + ResponseData::Error(Error { + kind: ErrorKind::NotFound, + .. + }) + ), + "Unexpected response: {:?}", + res.payload[0] + ); +} diff --git a/tests/cli/action/file_append.rs b/tests/cli/action/file_append.rs index 801280c..bd7d93c 100644 --- a/tests/cli/action/file_append.rs +++ b/tests/cli/action/file_append.rs @@ -45,6 +45,28 @@ fn should_report_ok_when_done(mut action_cmd: Command) { file.assert(format!("{}{}", FILE_CONTENTS, APPENDED_FILE_CONTENTS)); } +#[rstest] +fn yield_an_error_when_fails(mut action_cmd: Command) { + let temp = assert_fs::TempDir::new().unwrap(); + let file = temp.child("missing-dir").child("missing-file"); + + // distant action file-append {path} -- {contents} + action_cmd + .args(&[ + "file-append", + file.to_str().unwrap(), + "--", + APPENDED_FILE_CONTENTS, + ]) + .assert() + .code(ExitCode::Software.to_i32()) + .stdout("") + .stderr(FAILURE_LINE.clone()); + + // Because we're talking to a local server, we can verify locally + file.assert(predicates::path::missing()); +} + #[rstest] fn should_support_json_output(mut action_cmd: Command) { let temp = assert_fs::TempDir::new().unwrap(); @@ -80,28 +102,6 @@ fn should_support_json_output(mut action_cmd: Command) { file.assert(format!("{}{}", FILE_CONTENTS, APPENDED_FILE_CONTENTS)); } -#[rstest] -fn yield_an_error_when_fails(mut action_cmd: Command) { - let temp = assert_fs::TempDir::new().unwrap(); - let file = temp.child("missing-dir").child("missing-file"); - - // distant action file-append {path} -- {contents} - action_cmd - .args(&[ - "file-append", - file.to_str().unwrap(), - "--", - APPENDED_FILE_CONTENTS, - ]) - .assert() - .code(ExitCode::Software.to_i32()) - .stdout("") - .stderr(FAILURE_LINE.clone()); - - // Because we're talking to a local server, we can verify locally - file.assert(predicates::path::missing()); -} - #[rstest] fn should_support_json_output_for_error(mut action_cmd: Command) { let temp = assert_fs::TempDir::new().unwrap(); diff --git a/tests/cli/action/file_append_text.rs b/tests/cli/action/file_append_text.rs index 1496ae7..80e2203 100644 --- a/tests/cli/action/file_append_text.rs +++ b/tests/cli/action/file_append_text.rs @@ -45,6 +45,28 @@ fn should_report_ok_when_done(mut action_cmd: Command) { file.assert(format!("{}{}", FILE_CONTENTS, APPENDED_FILE_CONTENTS)); } +#[rstest] +fn yield_an_error_when_fails(mut action_cmd: Command) { + let temp = assert_fs::TempDir::new().unwrap(); + let file = temp.child("missing-dir").child("missing-file"); + + // distant action file-append-text {path} -- {contents} + action_cmd + .args(&[ + "file-append-text", + file.to_str().unwrap(), + "--", + APPENDED_FILE_CONTENTS, + ]) + .assert() + .code(ExitCode::Software.to_i32()) + .stdout("") + .stderr(FAILURE_LINE.clone()); + + // Because we're talking to a local server, we can verify locally + file.assert(predicates::path::missing()); +} + #[rstest] fn should_support_json_output(mut action_cmd: Command) { let temp = assert_fs::TempDir::new().unwrap(); @@ -80,28 +102,6 @@ fn should_support_json_output(mut action_cmd: Command) { file.assert(format!("{}{}", FILE_CONTENTS, APPENDED_FILE_CONTENTS)); } -#[rstest] -fn yield_an_error_when_fails(mut action_cmd: Command) { - let temp = assert_fs::TempDir::new().unwrap(); - let file = temp.child("missing-dir").child("missing-file"); - - // distant action file-append-text {path} -- {contents} - action_cmd - .args(&[ - "file-append-text", - file.to_str().unwrap(), - "--", - APPENDED_FILE_CONTENTS, - ]) - .assert() - .code(ExitCode::Software.to_i32()) - .stdout("") - .stderr(FAILURE_LINE.clone()); - - // Because we're talking to a local server, we can verify locally - file.assert(predicates::path::missing()); -} - #[rstest] fn should_support_json_output_for_error(mut action_cmd: Command) { let temp = assert_fs::TempDir::new().unwrap(); diff --git a/tests/cli/action/file_read.rs b/tests/cli/action/file_read.rs index b9242c4..3d4a5ff 100644 --- a/tests/cli/action/file_read.rs +++ b/tests/cli/action/file_read.rs @@ -32,6 +32,20 @@ fn should_print_out_file_contents(mut action_cmd: Command) { .stderr(""); } +#[rstest] +fn yield_an_error_when_fails(mut action_cmd: Command) { + let temp = assert_fs::TempDir::new().unwrap(); + let file = temp.child("missing-file"); + + // distant action file-read {path} + action_cmd + .args(&["file-read", file.to_str().unwrap()]) + .assert() + .code(ExitCode::Software.to_i32()) + .stdout("") + .stderr(FAILURE_LINE.clone()); +} + #[rstest] fn should_support_json_output(mut action_cmd: Command) { let temp = assert_fs::TempDir::new().unwrap(); @@ -64,20 +78,6 @@ fn should_support_json_output(mut action_cmd: Command) { ); } -#[rstest] -fn yield_an_error_when_fails(mut action_cmd: Command) { - let temp = assert_fs::TempDir::new().unwrap(); - let file = temp.child("missing-file"); - - // distant action file-read {path} - action_cmd - .args(&["file-read", file.to_str().unwrap()]) - .assert() - .code(ExitCode::Software.to_i32()) - .stdout("") - .stderr(FAILURE_LINE.clone()); -} - #[rstest] fn should_support_json_output_for_error(mut action_cmd: Command) { let temp = assert_fs::TempDir::new().unwrap(); diff --git a/tests/cli/action/file_read_text.rs b/tests/cli/action/file_read_text.rs index a084f87..a4bc9cf 100644 --- a/tests/cli/action/file_read_text.rs +++ b/tests/cli/action/file_read_text.rs @@ -32,6 +32,20 @@ fn should_print_out_file_contents(mut action_cmd: Command) { .stderr(""); } +#[rstest] +fn yield_an_error_when_fails(mut action_cmd: Command) { + let temp = assert_fs::TempDir::new().unwrap(); + let file = temp.child("missing-file"); + + // distant action file-read-text {path} + action_cmd + .args(&["file-read-text", file.to_str().unwrap()]) + .assert() + .code(ExitCode::Software.to_i32()) + .stdout("") + .stderr(FAILURE_LINE.clone()); +} + #[rstest] fn should_support_json_output(mut action_cmd: Command) { let temp = assert_fs::TempDir::new().unwrap(); @@ -64,20 +78,6 @@ fn should_support_json_output(mut action_cmd: Command) { ); } -#[rstest] -fn yield_an_error_when_fails(mut action_cmd: Command) { - let temp = assert_fs::TempDir::new().unwrap(); - let file = temp.child("missing-file"); - - // distant action file-read-text {path} - action_cmd - .args(&["file-read-text", file.to_str().unwrap()]) - .assert() - .code(ExitCode::Software.to_i32()) - .stdout("") - .stderr(FAILURE_LINE.clone()); -} - #[rstest] fn should_support_json_output_for_error(mut action_cmd: Command) { let temp = assert_fs::TempDir::new().unwrap(); diff --git a/tests/cli/action/file_write.rs b/tests/cli/action/file_write.rs index a315e2b..e2f79ca 100644 --- a/tests/cli/action/file_write.rs +++ b/tests/cli/action/file_write.rs @@ -34,6 +34,23 @@ fn should_report_ok_when_done(mut action_cmd: Command) { file.assert(FILE_CONTENTS); } +#[rstest] +fn yield_an_error_when_fails(mut action_cmd: Command) { + let temp = assert_fs::TempDir::new().unwrap(); + let file = temp.child("missing-dir").child("missing-file"); + + // distant action file-write {path} -- {contents} + action_cmd + .args(&["file-write", file.to_str().unwrap(), "--", FILE_CONTENTS]) + .assert() + .code(ExitCode::Software.to_i32()) + .stdout("") + .stderr(FAILURE_LINE.clone()); + + // Because we're talking to a local server, we can verify locally + file.assert(predicates::path::missing()); +} + #[rstest] fn should_support_json_output(mut action_cmd: Command) { let temp = assert_fs::TempDir::new().unwrap(); @@ -68,23 +85,6 @@ fn should_support_json_output(mut action_cmd: Command) { file.assert(FILE_CONTENTS); } -#[rstest] -fn yield_an_error_when_fails(mut action_cmd: Command) { - let temp = assert_fs::TempDir::new().unwrap(); - let file = temp.child("missing-dir").child("missing-file"); - - // distant action file-write {path} -- {contents} - action_cmd - .args(&["file-write", file.to_str().unwrap(), "--", FILE_CONTENTS]) - .assert() - .code(ExitCode::Software.to_i32()) - .stdout("") - .stderr(FAILURE_LINE.clone()); - - // Because we're talking to a local server, we can verify locally - file.assert(predicates::path::missing()); -} - #[rstest] fn should_support_json_output_for_error(mut action_cmd: Command) { let temp = assert_fs::TempDir::new().unwrap(); diff --git a/tests/cli/action/file_write_text.rs b/tests/cli/action/file_write_text.rs index 12f9546..35de2dc 100644 --- a/tests/cli/action/file_write_text.rs +++ b/tests/cli/action/file_write_text.rs @@ -39,6 +39,28 @@ fn should_report_ok_when_done(mut action_cmd: Command) { file.assert(FILE_CONTENTS); } +#[rstest] +fn yield_an_error_when_fails(mut action_cmd: Command) { + let temp = assert_fs::TempDir::new().unwrap(); + let file = temp.child("missing-dir").child("missing-file"); + + // distant action file-write {path} -- {contents} + action_cmd + .args(&[ + "file-write-text", + file.to_str().unwrap(), + "--", + FILE_CONTENTS, + ]) + .assert() + .code(ExitCode::Software.to_i32()) + .stdout("") + .stderr(FAILURE_LINE.clone()); + + // Because we're talking to a local server, we can verify locally + file.assert(predicates::path::missing()); +} + #[rstest] fn should_support_json_output(mut action_cmd: Command) { let temp = assert_fs::TempDir::new().unwrap(); @@ -73,28 +95,6 @@ fn should_support_json_output(mut action_cmd: Command) { file.assert(FILE_CONTENTS); } -#[rstest] -fn yield_an_error_when_fails(mut action_cmd: Command) { - let temp = assert_fs::TempDir::new().unwrap(); - let file = temp.child("missing-dir").child("missing-file"); - - // distant action file-write {path} -- {contents} - action_cmd - .args(&[ - "file-write-text", - file.to_str().unwrap(), - "--", - FILE_CONTENTS, - ]) - .assert() - .code(ExitCode::Software.to_i32()) - .stdout("") - .stderr(FAILURE_LINE.clone()); - - // Because we're talking to a local server, we can verify locally - file.assert(predicates::path::missing()); -} - #[rstest] fn should_support_json_output_for_error(mut action_cmd: Command) { let temp = assert_fs::TempDir::new().unwrap(); diff --git a/tests/cli/action/mod.rs b/tests/cli/action/mod.rs index 23c30e0..c18a796 100644 --- a/tests/cli/action/mod.rs +++ b/tests/cli/action/mod.rs @@ -1,6 +1,10 @@ +mod copy; +mod dir_create; +mod dir_read; mod file_append; mod file_append_text; mod file_read; mod file_read_text; mod file_write; mod file_write_text; +mod remove; diff --git a/tests/cli/action/remove.rs b/tests/cli/action/remove.rs new file mode 100644 index 0000000..9ea27d9 --- /dev/null +++ b/tests/cli/action/remove.rs @@ -0,0 +1,229 @@ +use crate::cli::{ + fixtures::*, + utils::{random_tenant, FAILURE_LINE}, +}; +use assert_cmd::Command; +use assert_fs::prelude::*; +use distant::ExitCode; +use distant_core::{ + data::{Error, ErrorKind}, + Request, RequestData, Response, ResponseData, +}; +use predicates::prelude::*; +use rstest::*; + +#[rstest] +fn should_support_removing_file(mut action_cmd: Command) { + let temp = assert_fs::TempDir::new().unwrap(); + let file = temp.child("file"); + file.touch().unwrap(); + + // distant action remove {path} + action_cmd + .args(&["remove", file.to_str().unwrap()]) + .assert() + .success() + .stdout("") + .stderr(""); + + file.assert(predicate::path::missing()); +} + +#[rstest] +fn should_support_removing_empty_directory(mut action_cmd: Command) { + let temp = assert_fs::TempDir::new().unwrap(); + + // Make an empty directory + let dir = temp.child("dir"); + dir.create_dir_all().unwrap(); + + // distant action remove {path} + action_cmd + .args(&["remove", dir.to_str().unwrap()]) + .assert() + .success() + .stdout("") + .stderr(""); + + dir.assert(predicate::path::missing()); +} + +#[rstest] +fn should_support_removing_nonempty_directory_if_force_specified(mut action_cmd: Command) { + let temp = assert_fs::TempDir::new().unwrap(); + + // Make a non-empty directory + let dir = temp.child("dir"); + dir.create_dir_all().unwrap(); + dir.child("file").touch().unwrap(); + + // distant action remove --force {path} + action_cmd + .args(&["remove", "--force", dir.to_str().unwrap()]) + .assert() + .success() + .stdout("") + .stderr(""); + + dir.assert(predicate::path::missing()); +} + +#[rstest] +fn yield_an_error_when_fails(mut action_cmd: Command) { + let temp = assert_fs::TempDir::new().unwrap(); + + // Make a non-empty directory + let dir = temp.child("dir"); + dir.create_dir_all().unwrap(); + dir.child("file").touch().unwrap(); + + // distant action remove {path} + action_cmd + .args(&["remove", dir.to_str().unwrap()]) + .assert() + .code(ExitCode::Software.to_i32()) + .stdout("") + .stderr(FAILURE_LINE.clone()); + + dir.assert(predicate::path::exists()); + dir.assert(predicate::path::is_dir()); +} + +#[rstest] +fn should_support_json_removing_file(mut action_cmd: Command) { + let temp = assert_fs::TempDir::new().unwrap(); + + let file = temp.child("file"); + file.touch().unwrap(); + + let req = Request { + id: rand::random(), + tenant: random_tenant(), + payload: vec![RequestData::Remove { + path: file.to_path_buf(), + force: false, + }], + }; + + // distant action --format json --interactive + let cmd = action_cmd + .args(&["--format", "json"]) + .arg("--interactive") + .write_stdin(format!("{}\n", serde_json::to_string(&req).unwrap())) + .assert() + .success() + .stderr(""); + + let res: Response = serde_json::from_slice(&cmd.get_output().stdout).unwrap(); + assert_eq!(res.payload[0], ResponseData::Ok); + + file.assert(predicate::path::missing()); +} + +#[rstest] +fn should_support_json_removing_empty_directory(mut action_cmd: Command) { + let temp = assert_fs::TempDir::new().unwrap(); + + // Make an empty directory + let dir = temp.child("dir"); + dir.create_dir_all().unwrap(); + + let req = Request { + id: rand::random(), + tenant: random_tenant(), + payload: vec![RequestData::Remove { + path: dir.to_path_buf(), + force: false, + }], + }; + + // distant action --format json --interactive + let cmd = action_cmd + .args(&["--format", "json"]) + .arg("--interactive") + .write_stdin(format!("{}\n", serde_json::to_string(&req).unwrap())) + .assert() + .success() + .stderr(""); + + let res: Response = serde_json::from_slice(&cmd.get_output().stdout).unwrap(); + assert_eq!(res.payload[0], ResponseData::Ok); + + dir.assert(predicate::path::missing()); +} + +#[rstest] +fn should_support_json_removing_nonempty_directory_if_force_specified(mut action_cmd: Command) { + let temp = assert_fs::TempDir::new().unwrap(); + + // Make an empty directory + let dir = temp.child("dir"); + dir.create_dir_all().unwrap(); + + let req = Request { + id: rand::random(), + tenant: random_tenant(), + payload: vec![RequestData::Remove { + path: dir.to_path_buf(), + force: true, + }], + }; + + // distant action --format json --interactive + let cmd = action_cmd + .args(&["--format", "json"]) + .arg("--interactive") + .write_stdin(format!("{}\n", serde_json::to_string(&req).unwrap())) + .assert() + .success() + .stderr(""); + + let res: Response = serde_json::from_slice(&cmd.get_output().stdout).unwrap(); + assert_eq!(res.payload[0], ResponseData::Ok); + + dir.assert(predicate::path::missing()); +} + +#[rstest] +fn should_support_json_output_for_error(mut action_cmd: Command) { + let temp = assert_fs::TempDir::new().unwrap(); + + // Make a non-empty directory + let dir = temp.child("dir"); + dir.create_dir_all().unwrap(); + dir.child("file").touch().unwrap(); + + let req = Request { + id: rand::random(), + tenant: random_tenant(), + payload: vec![RequestData::Remove { + path: dir.to_path_buf(), + force: false, + }], + }; + + // distant action --format json --interactive + let cmd = action_cmd + .args(&["--format", "json"]) + .arg("--interactive") + .write_stdin(format!("{}\n", serde_json::to_string(&req).unwrap())) + .assert() + .success() + .stderr(""); + + let res: Response = serde_json::from_slice(&cmd.get_output().stdout).unwrap(); + assert!( + matches!( + res.payload[0], + ResponseData::Error(Error { + kind: ErrorKind::Other, + .. + }) + ), + "Unexpected response: {:?}", + res.payload[0] + ); + + dir.assert(predicate::path::exists()); + dir.assert(predicate::path::is_dir()); +}