From 1c58f1d62355ae2280c324ef67bac6ce338c5b95 Mon Sep 17 00:00:00 2001 From: veeso Date: Sat, 12 Jun 2021 16:17:47 +0200 Subject: [PATCH] Use containers to test file transfers Use containers to test file transfers Container setup Container setup tests with docker-compose these tests won't work with containers ftp tests with containers; removed crap servers; tests only lib hostname for github booooooh fixed recursive remove FTP Use containers to test file transfers Container setup Container setup tests with docker-compose these tests won't work with containers ftp tests with containers; removed crap servers; tests only lib hostname for github booooooh fixed recursive remove FTP fixed rename changelog Desperate attempt Fixed ftp tests; migrated sftp tests to containers; use env for services github actions are just broken imo github actions are just broken imo Don't use services, since they just don't fuckin work... docker compose not supported yet? deprecated typo Now explain this: github actions have services (which don't work) and then you find out docker-compose is already installed *BLOWMIND* Fixed hostname for tests scp tests maybe tests wtf Restored host tests Changelog Improving coverage Improving coverage Restored coverage task More tests for file transfers; test ssh keys too typo tests; code improvements Use tests helpers fixed tempdir fixed tempdir --- .github/workflows/coverage.yml | 17 +- .github/workflows/linux.yml | 7 +- .github/workflows/macos.yml | 2 +- .github/workflows/windows.yml | 2 +- CHANGELOG.md | 6 + Cargo.toml | 3 +- docs/developer.md | 19 +- src/config/mod.rs | 6 + src/config/serializer.rs | 19 + src/filetransfer/ftp_transfer.rs | 696 +++++++++++++++--------------- src/filetransfer/mod.rs | 5 +- src/filetransfer/scp_transfer.rs | 536 ++++++++++++----------- src/filetransfer/sftp_transfer.rs | 552 ++++++++++-------------- src/fs/mod.rs | 64 +++ src/host/mod.rs | 94 ++-- src/system/bookmarks_client.rs | 29 +- src/system/config_client.rs | 30 +- src/system/sshkey_storage.rs | 29 +- src/ui/context.rs | 2 +- src/utils/git.rs | 2 +- src/utils/mod.rs | 4 + src/utils/parser.rs | 7 +- src/utils/test_helpers.rs | 248 +++++++++++ tests/docker-compose.yml | 35 ++ tests/test.sh | 29 ++ 25 files changed, 1393 insertions(+), 1050 deletions(-) create mode 100644 src/utils/test_helpers.rs create mode 100644 tests/docker-compose.yml create mode 100755 tests/test.sh diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index f491d62..49fff47 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -1,4 +1,4 @@ -name: coverage +name: Coverage on: [push, pull_request] @@ -6,22 +6,23 @@ env: CARGO_TERM_COLOR: always jobs: - coverage: - name: Generate coverage + build: runs-on: ubuntu-latest + steps: - - name: Checkout repository - uses: actions/checkout@v2 - - name: Setup rust toolchain + - uses: actions/checkout@v2 + - name: Setup containers + run: docker-compose -f "tests/docker-compose.yml" up -d --build + - name: Setup nightly toolchain uses: actions-rs/toolchain@v1 with: toolchain: nightly override: true - - name: Run tests + - name: Run tests (nightly) uses: actions-rs/cargo@v1 with: command: test - args: --all-features --no-fail-fast + args: --all-features --lib --no-fail-fast env: CARGO_INCREMENTAL: "0" RUSTFLAGS: "-Zprofile -Ccodegen-units=1 -Cinline-threshold=0 -Clink-dead-code -Coverflow-checks=off -Cpanic=abort -Zpanic_abort_tests" diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index 374007b..9ac9f46 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -11,15 +11,18 @@ jobs: steps: - uses: actions/checkout@v2 + - name: Setup containers + run: docker-compose -f "tests/docker-compose.yml" up -d --build - uses: actions-rs/toolchain@v1 with: toolchain: stable override: true components: rustfmt, clippy - - uses: actions-rs/cargo@v1 + - name: Run tests + uses: actions-rs/cargo@v1 with: command: test - args: --all-features --no-fail-fast + args: --all-features --lib --no-fail-fast - name: Format run: cargo fmt --all -- --check - name: Clippy diff --git a/.github/workflows/macos.yml b/.github/workflows/macos.yml index dbe3b63..f77e5ad 100644 --- a/.github/workflows/macos.yml +++ b/.github/workflows/macos.yml @@ -14,6 +14,6 @@ jobs: - name: Build run: cargo build --verbose - name: Run tests - run: cargo test --verbose --features githubActions -- --test-threads 1 + run: cargo test --verbose --lib --features github-actions -- --test-threads 1 - name: Clippy run: cargo clippy diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index 7e685ec..80a9fc8 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -14,6 +14,6 @@ jobs: - name: Build run: cargo build --verbose - name: Run tests - run: cargo test --verbose --features githubActions -- --test-threads 1 + run: cargo test --verbose --lib --features github-actions -- --test-threads 1 - name: Clippy run: cargo clippy diff --git a/CHANGELOG.md b/CHANGELOG.md index 4161e31..3cab7cb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,12 @@ Released on FIXME: ?? +- Enhancements: + - **CI now uses containers to test file transfers (SSH/FTP)** + - Improved coverage + - Found many bugs which has now been fixed + - Build in CI won't fail due to test servers not responding + - We're now able to test all the functionalities of the file transfers - Bugfix: - Fixed broken input cursor when typing UTF8 characters (tui-realm 0.3.2) - Fixed [Issue 44](https://github.com/veeso/termscp/issues/44): Could not move files to other paths in FTP diff --git a/Cargo.toml b/Cargo.toml index 931d8dc..c504784 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -59,7 +59,8 @@ wildmatch = "2.0.0" pretty_assertions = "0.7.2" [features] -githubActions = [] +github-actions = [] +with-containers = [] [target."cfg(any(target_os = \"unix\", target_os = \"macos\", target_os = \"linux\"))"] [target."cfg(any(target_os = \"unix\", target_os = \"macos\", target_os = \"linux\"))".dependencies] diff --git a/docs/developer.md b/docs/developer.md index e342956..a382fca 100644 --- a/docs/developer.md +++ b/docs/developer.md @@ -3,15 +3,26 @@ Document audience: developers - [Developer Manual](#developer-manual) + - [How to test](#how-to-test) - [How termscp works](#how-termscp-works) - [Activities](#activities) - [The Context](#the-context) - - [Tests fails due to receivers](#tests-fails-due-to-receivers) - [Implementing File Transfers](#implementing-file-transfers) Welcome to the developer manual for termscp. This chapter DOESN'T contain the documentation for termscp modules, which can instead be found on Rust Docs at This chapter describes how termscp works and the guide lines to implement stuff such as file transfers and add features to the user interface. +## How to test + +First an introduction to tests. + +Usually it's enough to run `cargo test`, but please note that whenever you're working on file transfer you'll need one more step. +In order to run tests with file transfers, you need to start the file transfer server containers, which can be started via `docker`. + +To run all tests with file transfers just run: `./tests/test.sh` + +--- + ## How termscp works termscp is basically made up of 4 components: @@ -62,12 +73,6 @@ The context basically holds the following data: --- -## Tests fails due to receivers - -Yes. This happens quite often and is related to the fact that I'm using public SSH/SFTP/FTP server to test file receivers and sometimes this server go down for even a day or more. If your tests don't pass due to this, don't worry, submit the pull request and I'll take care of testing them by myself. - ---- - ## Implementing File Transfers This chapter describes how to implement a file transfer in termscp. A file transfer is a module which implements the `FileTransfer` trait. The file transfer provides different modules to interact with a remote server, which in addition to the most obvious methods, used to download and upload files, provides also methods to list files, delete files, create directories etc. diff --git a/src/config/mod.rs b/src/config/mod.rs index 4153bc2..14c866e 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -183,6 +183,12 @@ mod tests { file_fmt: Some(String::from("{NAME}")), remote_file_fmt: Some(String::from("{USER}")), }; + assert_eq!(ui.default_protocol, String::from("SFTP")); + assert_eq!(ui.text_editor, PathBuf::from("nano")); + assert_eq!(ui.show_hidden_files, true); + assert_eq!(ui.check_for_updates, Some(true)); + assert_eq!(ui.group_dirs, Some(String::from("first"))); + assert_eq!(ui.file_fmt, Some(String::from("{NAME}"))); let cfg: UserConfig = UserConfig { user_interface: ui, remote: remote, diff --git a/src/config/serializer.rs b/src/config/serializer.rs index 2375243..5ec89f4 100644 --- a/src/config/serializer.rs +++ b/src/config/serializer.rs @@ -208,6 +208,25 @@ mod tests { assert!(serializer.deserialize(Box::new(toml_file)).is_ok()); } + #[test] + fn test_config_serializer_fail_write() { + let toml_file: tempfile::NamedTempFile = tempfile::NamedTempFile::new().ok().unwrap(); + let writer: Box = Box::new(std::fs::File::open(toml_file.path()).unwrap()); + // Try to write unexisting file + let serializer: ConfigSerializer = ConfigSerializer {}; + let cfg: UserConfig = UserConfig::default(); + assert!(serializer.serialize(writer, &cfg).is_err()); + } + + #[test] + fn test_config_serializer_fail_read() { + let toml_file: tempfile::NamedTempFile = tempfile::NamedTempFile::new().ok().unwrap(); + let reader: Box = Box::new(std::fs::File::open(toml_file.path()).unwrap()); + // Try to write unexisting file + let serializer: ConfigSerializer = ConfigSerializer {}; + assert!(serializer.deserialize(reader).is_err()); + } + fn create_good_toml() -> tempfile::NamedTempFile { // Write let mut tmpfile: tempfile::NamedTempFile = tempfile::NamedTempFile::new().unwrap(); diff --git a/src/filetransfer/ftp_transfer.rs b/src/filetransfer/ftp_transfer.rs index 6738a1e..919a9a0 100644 --- a/src/filetransfer/ftp_transfer.rs +++ b/src/filetransfer/ftp_transfer.rs @@ -491,7 +491,10 @@ impl FileTransfer for FtpFileTransfer { info!("Disconnecting from FTP server..."); match &mut self.stream { Some(stream) => match stream.quit() { - Ok(_) => Ok(()), + Ok(_) => { + self.stream = None; + Ok(()) + } Err(err) => Err(FileTransferError::new_ex( FileTransferErrorType::ConnectionError, err.to_string(), @@ -859,9 +862,14 @@ impl FileTransfer for FtpFileTransfer { mod tests { use super::*; + use crate::utils::file::open_file; use crate::utils::fmt::fmt_time; + #[cfg(feature = "with-containers")] + use crate::utils::test_helpers::write_file; + use crate::utils::test_helpers::{create_sample_file_entry, make_fsentry}; use pretty_assertions::assert_eq; + use std::io::{Read, Write}; use std::time::Duration; #[test] @@ -875,120 +883,281 @@ mod tests { assert!(ftp.stream.is_none()); } + #[test] + #[cfg(feature = "with-containers")] + fn test_filetransfer_ftp_server() { + let mut ftp: FtpFileTransfer = FtpFileTransfer::new(false); + // Sample file + let (entry, file): (FsFile, tempfile::NamedTempFile) = create_sample_file_entry(); + // Connect + #[cfg(not(feature = "github-actions"))] + let hostname: String = String::from("127.0.0.1"); + #[cfg(feature = "github-actions")] + let hostname: String = String::from("127.0.0.1"); + assert!(ftp + .connect( + hostname, + 10021, + Some(String::from("test")), + Some(String::from("test")), + ) + .is_ok()); + assert_eq!(ftp.is_connected(), true); + // Get pwd + assert_eq!(ftp.pwd().unwrap(), PathBuf::from("/")); + // List dir (dir is empty) + assert_eq!(ftp.list_dir(&Path::new("/")).unwrap().len(), 0); + // Make directory + assert!(ftp.mkdir(PathBuf::from("/home").as_path()).is_ok()); + // Make directory (err) + assert!(ftp.mkdir(PathBuf::from("/root/pommlar").as_path()).is_err()); + // Change directory + assert!(ftp.change_dir(PathBuf::from("/home").as_path()).is_ok()); + // Change directory (err) + assert!(ftp + .change_dir(PathBuf::from("/tmp/oooo/aaaa/eee").as_path()) + .is_err()); + // Copy (not supported) + assert!(ftp + .copy(&FsEntry::File(entry.clone()), PathBuf::from("/").as_path()) + .is_err()); + // Exec (not supported) + assert!(ftp.exec("echo 1;").is_err()); + // Upload 2 files + let mut writable = ftp + .send_file(&entry, PathBuf::from("omar.txt").as_path()) + .ok() + .unwrap(); + write_file(&file, &mut writable); + assert!(ftp.on_sent(writable).is_ok()); + let mut writable = ftp + .send_file(&entry, PathBuf::from("README.md").as_path()) + .ok() + .unwrap(); + write_file(&file, &mut writable); + assert!(ftp.on_sent(writable).is_ok()); + // Upload file (err) + assert!(ftp + .send_file(&entry, PathBuf::from("/ommlar/omarone").as_path()) + .is_err()); + // List dir + let list: Vec = ftp.list_dir(PathBuf::from("/home").as_path()).ok().unwrap(); + assert_eq!(list.len(), 2); + // Find + assert!(ftp.change_dir(PathBuf::from("/").as_path()).is_ok()); + assert_eq!(ftp.find("*.txt").ok().unwrap().len(), 1); + assert_eq!(ftp.find("*.md").ok().unwrap().len(), 1); + assert_eq!(ftp.find("*.jpeg").ok().unwrap().len(), 0); + assert!(ftp.change_dir(PathBuf::from("/home").as_path()).is_ok()); + // Rename + assert!(ftp.mkdir(PathBuf::from("/uploads").as_path()).is_ok()); + assert!(ftp + .rename( + list.get(0).unwrap(), + PathBuf::from("/uploads/README.txt").as_path() + ) + .is_ok()); + // Rename (err) + assert!(ftp + .rename(list.get(0).unwrap(), PathBuf::from("OMARONE").as_path()) + .is_err()); + let dummy: FsEntry = FsEntry::File(FsFile { + name: String::from("cucumber.txt"), + abs_path: PathBuf::from("/cucumber.txt"), + last_change_time: SystemTime::UNIX_EPOCH, + last_access_time: SystemTime::UNIX_EPOCH, + creation_time: SystemTime::UNIX_EPOCH, + size: 0, + ftype: Some(String::from("txt")), // File type + readonly: true, + symlink: None, // UNIX only + user: Some(0), // UNIX only + group: Some(0), // UNIX only + unix_pex: Some((6, 4, 4)), // UNIX only + }); + assert!(ftp + .rename(&dummy, PathBuf::from("/a/b/c").as_path()) + .is_err()); + // Remove + assert!(ftp.remove(list.get(1).unwrap()).is_ok()); + assert!(ftp.remove(list.get(1).unwrap()).is_err()); + // Receive file + let mut writable = ftp + .send_file(&entry, PathBuf::from("/uploads/README.txt").as_path()) + .ok() + .unwrap(); + write_file(&file, &mut writable); + assert!(ftp.on_sent(writable).is_ok()); + let file: FsFile = ftp + .list_dir(PathBuf::from("/uploads").as_path()) + .ok() + .unwrap() + .get(0) + .unwrap() + .clone() + .unwrap_file(); + let mut readable = ftp.recv_file(&file).ok().unwrap(); + let mut data: Vec = vec![0; 1024]; + assert!(readable.read(&mut data).is_ok()); + assert!(ftp.on_recv(readable).is_ok()); + // Receive file (err) + assert!(ftp.recv_file(&entry).is_err()); + // Cleanup + assert!(ftp.change_dir(PathBuf::from("/").as_path()).is_ok()); + assert!(ftp + .remove(&make_fsentry(PathBuf::from("/home"), true)) + .is_ok()); + assert!(ftp + .remove(&make_fsentry(PathBuf::from("/uploads"), true)) + .is_ok()); + // Disconnect + assert!(ftp.disconnect().is_ok()); + assert_eq!(ftp.is_connected(), false); + } + + #[test] + #[cfg(feature = "with-containers")] + fn test_filetransfer_ftp_server_bad_auth() { + let mut ftp: FtpFileTransfer = FtpFileTransfer::new(false); + // Connect + assert!(ftp + .connect( + String::from("127.0.0.1"), + 10021, + Some(String::from("omar")), + Some(String::from("ommlar")), + ) + .is_err()); + } + + #[test] + #[cfg(feature = "with-containers")] + fn test_filetransfer_ftp_no_credentials() { + let mut ftp: FtpFileTransfer = FtpFileTransfer::new(false); + assert!(ftp + .connect(String::from("127.0.0.1"), 10021, None, None) + .is_err()); + } + + #[test] + fn test_filetransfer_ftp_server_bad_server() { + let mut ftp: FtpFileTransfer = FtpFileTransfer::new(false); + // Connect + assert!(ftp + .connect( + String::from("mybadserver.veryverybad.awful"), + 21, + Some(String::from("omar")), + Some(String::from("ommlar")), + ) + .is_err()); + } + #[test] fn test_filetransfer_ftp_parse_list_line_unix() { let mut ftp: FtpFileTransfer = FtpFileTransfer::new(false); // Simple file - let fs_entry: FsEntry = ftp + let file: FsFile = ftp .parse_list_line( PathBuf::from("/tmp").as_path(), "-rw-rw-r-- 1 root dialout 8192 Nov 5 2018 omar.txt", ) .ok() - .unwrap(); - if let FsEntry::File(file) = fs_entry { - assert_eq!(file.abs_path, PathBuf::from("/tmp/omar.txt")); - assert_eq!(file.name, String::from("omar.txt")); - assert_eq!(file.size, 8192); - assert!(file.symlink.is_none()); - assert_eq!(file.user, None); - assert_eq!(file.group, None); - assert_eq!(file.unix_pex.unwrap(), (6, 6, 4)); - assert_eq!( - file.last_access_time - .duration_since(SystemTime::UNIX_EPOCH) - .ok() - .unwrap(), - Duration::from_secs(1541376000) - ); - assert_eq!( - file.last_change_time - .duration_since(SystemTime::UNIX_EPOCH) - .ok() - .unwrap(), - Duration::from_secs(1541376000) - ); - assert_eq!( - file.creation_time - .duration_since(SystemTime::UNIX_EPOCH) - .ok() - .unwrap(), - Duration::from_secs(1541376000) - ); - } else { - panic!("Expected file, got directory"); - } + .unwrap() + .unwrap_file(); + assert_eq!(file.abs_path, PathBuf::from("/tmp/omar.txt")); + assert_eq!(file.name, String::from("omar.txt")); + assert_eq!(file.size, 8192); + assert!(file.symlink.is_none()); + assert_eq!(file.user, None); + assert_eq!(file.group, None); + assert_eq!(file.unix_pex.unwrap(), (6, 6, 4)); + assert_eq!( + file.last_access_time + .duration_since(SystemTime::UNIX_EPOCH) + .ok() + .unwrap(), + Duration::from_secs(1541376000) + ); + assert_eq!( + file.last_change_time + .duration_since(SystemTime::UNIX_EPOCH) + .ok() + .unwrap(), + Duration::from_secs(1541376000) + ); + assert_eq!( + file.creation_time + .duration_since(SystemTime::UNIX_EPOCH) + .ok() + .unwrap(), + Duration::from_secs(1541376000) + ); // Simple file with number as gid, uid - let fs_entry: FsEntry = ftp + let file: FsFile = ftp .parse_list_line( PathBuf::from("/tmp").as_path(), "-rwxr-xr-x 1 0 9 4096 Nov 5 16:32 omar.txt", ) .ok() - .unwrap(); - if let FsEntry::File(file) = fs_entry { - assert_eq!(file.abs_path, PathBuf::from("/tmp/omar.txt")); - assert_eq!(file.name, String::from("omar.txt")); - assert_eq!(file.size, 4096); - assert!(file.symlink.is_none()); - assert_eq!(file.user, Some(0)); - assert_eq!(file.group, Some(9)); - assert_eq!(file.unix_pex.unwrap(), (7, 5, 5)); - assert_eq!( - fmt_time(file.last_access_time, "%m %d %M").as_str(), - "11 05 32" - ); - assert_eq!( - fmt_time(file.last_change_time, "%m %d %M").as_str(), - "11 05 32" - ); - assert_eq!( - fmt_time(file.creation_time, "%m %d %M").as_str(), - "11 05 32" - ); - } else { - panic!("Expected file, got directory"); - } + .unwrap() + .unwrap_file(); + assert_eq!(file.abs_path, PathBuf::from("/tmp/omar.txt")); + assert_eq!(file.name, String::from("omar.txt")); + assert_eq!(file.size, 4096); + assert!(file.symlink.is_none()); + assert_eq!(file.user, Some(0)); + assert_eq!(file.group, Some(9)); + assert_eq!(file.unix_pex.unwrap(), (7, 5, 5)); + assert_eq!( + fmt_time(file.last_access_time, "%m %d %M").as_str(), + "11 05 32" + ); + assert_eq!( + fmt_time(file.last_change_time, "%m %d %M").as_str(), + "11 05 32" + ); + assert_eq!( + fmt_time(file.creation_time, "%m %d %M").as_str(), + "11 05 32" + ); // Directory - let fs_entry: FsEntry = ftp + let dir: FsDirectory = ftp .parse_list_line( PathBuf::from("/tmp").as_path(), "drwxrwxr-x 1 0 9 4096 Nov 5 2018 docs", ) .ok() - .unwrap(); - if let FsEntry::Directory(dir) = fs_entry { - assert_eq!(dir.abs_path, PathBuf::from("/tmp/docs")); - assert_eq!(dir.name, String::from("docs")); - assert!(dir.symlink.is_none()); - assert_eq!(dir.user, Some(0)); - assert_eq!(dir.group, Some(9)); - assert_eq!(dir.unix_pex.unwrap(), (7, 7, 5)); - assert_eq!( - dir.last_access_time - .duration_since(SystemTime::UNIX_EPOCH) - .ok() - .unwrap(), - Duration::from_secs(1541376000) - ); - assert_eq!( - dir.last_change_time - .duration_since(SystemTime::UNIX_EPOCH) - .ok() - .unwrap(), - Duration::from_secs(1541376000) - ); - assert_eq!( - dir.creation_time - .duration_since(SystemTime::UNIX_EPOCH) - .ok() - .unwrap(), - Duration::from_secs(1541376000) - ); - assert_eq!(dir.readonly, false); - } else { - panic!("Expected directory, got directory"); - } + .unwrap() + .unwrap_dir(); + assert_eq!(dir.abs_path, PathBuf::from("/tmp/docs")); + assert_eq!(dir.name, String::from("docs")); + assert!(dir.symlink.is_none()); + assert_eq!(dir.user, Some(0)); + assert_eq!(dir.group, Some(9)); + assert_eq!(dir.unix_pex.unwrap(), (7, 7, 5)); + assert_eq!( + dir.last_access_time + .duration_since(SystemTime::UNIX_EPOCH) + .ok() + .unwrap(), + Duration::from_secs(1541376000) + ); + assert_eq!( + dir.last_change_time + .duration_since(SystemTime::UNIX_EPOCH) + .ok() + .unwrap(), + Duration::from_secs(1541376000) + ); + assert_eq!( + dir.creation_time + .duration_since(SystemTime::UNIX_EPOCH) + .ok() + .unwrap(), + Duration::from_secs(1541376000) + ); + assert_eq!(dir.readonly, false); // Error assert!(ftp .parse_list_line( @@ -1002,186 +1171,85 @@ mod tests { fn test_filetransfer_ftp_parse_list_line_dos() { let mut ftp: FtpFileTransfer = FtpFileTransfer::new(false); // Simple file - let fs_entry: FsEntry = ftp + let file: FsFile = ftp .parse_list_line( PathBuf::from("/tmp").as_path(), "04-08-14 03:09PM 8192 omar.txt", ) .ok() - .unwrap(); - if let FsEntry::File(file) = fs_entry { - assert_eq!(file.abs_path, PathBuf::from("/tmp/omar.txt")); - assert_eq!(file.name, String::from("omar.txt")); - assert_eq!(file.size, 8192); - assert!(file.symlink.is_none()); - assert_eq!(file.user, None); - assert_eq!(file.group, None); - assert_eq!(file.unix_pex, None); - assert_eq!( - file.last_access_time - .duration_since(SystemTime::UNIX_EPOCH) - .ok() - .unwrap(), - Duration::from_secs(1407164940) - ); - assert_eq!( - file.last_change_time - .duration_since(SystemTime::UNIX_EPOCH) - .ok() - .unwrap(), - Duration::from_secs(1407164940) - ); - assert_eq!( - file.creation_time - .duration_since(SystemTime::UNIX_EPOCH) - .ok() - .unwrap(), - Duration::from_secs(1407164940) - ); - } else { - panic!("Expected file, got directory"); - } + .unwrap() + .unwrap_file(); + assert_eq!(file.abs_path, PathBuf::from("/tmp/omar.txt")); + assert_eq!(file.name, String::from("omar.txt")); + assert_eq!(file.size, 8192); + assert!(file.symlink.is_none()); + assert_eq!(file.user, None); + assert_eq!(file.group, None); + assert_eq!(file.unix_pex, None); + assert_eq!( + file.last_access_time + .duration_since(SystemTime::UNIX_EPOCH) + .ok() + .unwrap(), + Duration::from_secs(1407164940) + ); + assert_eq!( + file.last_change_time + .duration_since(SystemTime::UNIX_EPOCH) + .ok() + .unwrap(), + Duration::from_secs(1407164940) + ); + assert_eq!( + file.creation_time + .duration_since(SystemTime::UNIX_EPOCH) + .ok() + .unwrap(), + Duration::from_secs(1407164940) + ); // Directory - let fs_entry: FsEntry = ftp + let dir: FsDirectory = ftp .parse_list_line( PathBuf::from("/tmp").as_path(), "04-08-14 03:09PM docs", ) .ok() - .unwrap(); - if let FsEntry::Directory(dir) = fs_entry { - assert_eq!(dir.abs_path, PathBuf::from("/tmp/docs")); - assert_eq!(dir.name, String::from("docs")); - assert!(dir.symlink.is_none()); - assert_eq!(dir.user, None); - assert_eq!(dir.group, None); - assert_eq!(dir.unix_pex, None); - assert_eq!( - dir.last_access_time - .duration_since(SystemTime::UNIX_EPOCH) - .ok() - .unwrap(), - Duration::from_secs(1407164940) - ); - assert_eq!( - dir.last_change_time - .duration_since(SystemTime::UNIX_EPOCH) - .ok() - .unwrap(), - Duration::from_secs(1407164940) - ); - assert_eq!( - dir.creation_time - .duration_since(SystemTime::UNIX_EPOCH) - .ok() - .unwrap(), - Duration::from_secs(1407164940) - ); - assert_eq!(dir.readonly, false); - } else { - panic!("Expected directory, got directory"); - } + .unwrap() + .unwrap_dir(); + assert_eq!(dir.abs_path, PathBuf::from("/tmp/docs")); + assert_eq!(dir.name, String::from("docs")); + assert!(dir.symlink.is_none()); + assert_eq!(dir.user, None); + assert_eq!(dir.group, None); + assert_eq!(dir.unix_pex, None); + assert_eq!( + dir.last_access_time + .duration_since(SystemTime::UNIX_EPOCH) + .ok() + .unwrap(), + Duration::from_secs(1407164940) + ); + assert_eq!( + dir.last_change_time + .duration_since(SystemTime::UNIX_EPOCH) + .ok() + .unwrap(), + Duration::from_secs(1407164940) + ); + assert_eq!( + dir.creation_time + .duration_since(SystemTime::UNIX_EPOCH) + .ok() + .unwrap(), + Duration::from_secs(1407164940) + ); + assert_eq!(dir.readonly, false); // Error assert!(ftp .parse_list_line(PathBuf::from("/").as_path(), "04-08-14 omar.txt") .is_err()); } - #[test] - fn test_filetransfer_ftp_connect_unsecure_anonymous() { - let mut ftp: FtpFileTransfer = FtpFileTransfer::new(false); - // Connect - assert!(ftp - .connect(String::from("speedtest.tele2.net"), 21, None, None) - .is_ok()); - // Pwd - assert_eq!(ftp.pwd().ok().unwrap(), PathBuf::from("/")); - // Disconnect - assert!(ftp.disconnect().is_ok()); - } - - #[test] - fn test_filetransfer_ftp_connect_unsecure_username() { - let mut ftp: FtpFileTransfer = FtpFileTransfer::new(false); - // Connect - assert!(ftp - .connect( - String::from("test.rebex.net"), - 21, - Some(String::from("demo")), - Some(String::from("password")) - ) - .is_ok()); - // Pwd - assert_eq!(ftp.pwd().ok().unwrap(), PathBuf::from("/")); - // Disconnect - assert!(ftp.disconnect().is_ok()); - } - - #[test] - fn test_filetransfer_ftp_connect_secure() { - let mut ftp: FtpFileTransfer = FtpFileTransfer::new(true); - // Connect - assert!(ftp - .connect( - String::from("test.rebex.net"), - 21, - Some(String::from("demo")), - Some(String::from("password")) - ) - .is_ok()); - // Pwd - assert_eq!(ftp.pwd().ok().unwrap(), PathBuf::from("/")); - // Disconnect - assert!(ftp.disconnect().is_ok()); - } - - #[test] - fn test_filetransfer_ftp_change_dir() { - let mut ftp: FtpFileTransfer = FtpFileTransfer::new(false); - // Connect - assert!(ftp - .connect(String::from("speedtest.tele2.net"), 21, None, None) - .is_ok()); - // Pwd - assert_eq!(ftp.pwd().ok().unwrap(), PathBuf::from("/")); - // Cwd - assert!(ftp.change_dir(PathBuf::from("upload/").as_path()).is_ok()); - // Pwd - assert_eq!(ftp.pwd().ok().unwrap(), PathBuf::from("/upload")); - // Disconnect - assert!(ftp.disconnect().is_ok()); - } - - #[test] - fn test_filetransfer_ftp_copy() { - let mut ftp: FtpFileTransfer = FtpFileTransfer::new(false); - // Connect - assert!(ftp - .connect(String::from("speedtest.tele2.net"), 21, None, None) - .is_ok()); - // Pwd - assert_eq!(ftp.pwd().ok().unwrap(), PathBuf::from("/")); - // Copy - let file: FsFile = FsFile { - name: String::from("readme.txt"), - abs_path: PathBuf::from("/readme.txt"), - last_change_time: SystemTime::UNIX_EPOCH, - last_access_time: SystemTime::UNIX_EPOCH, - creation_time: SystemTime::UNIX_EPOCH, - size: 0, - ftype: Some(String::from("txt")), // File type - readonly: true, - symlink: None, // UNIX only - user: Some(0), // UNIX only - group: Some(0), // UNIX only - unix_pex: Some((6, 4, 4)), // UNIX only - }; - assert!(ftp - .copy(&FsEntry::File(file), &Path::new("/tmp/dest.txt")) - .is_err()); - } - #[test] fn test_filetransfer_ftp_list_dir_dos_syntax() { let mut ftp: FtpFileTransfer = FtpFileTransfer::new(false); @@ -1205,94 +1273,16 @@ mod tests { } #[test] - #[cfg(not(target_os = "macos"))] - fn test_filetransfer_ftp_list_dir_unix_syntax() { - let mut ftp: FtpFileTransfer = FtpFileTransfer::new(false); - // Connect - assert!(ftp - .connect(String::from("speedtest.tele2.net"), 21, None, None) - .is_ok()); - // Pwd - assert_eq!(ftp.pwd().ok().unwrap(), PathBuf::from("/")); - // List dir - let files: Vec = ftp.list_dir(PathBuf::from("/").as_path()).ok().unwrap(); - // There should be at least 1 file - assert!(files.len() > 0); - // Disconnect - assert!(ftp.disconnect().is_ok()); - } - - /* NOTE: they don't work - #[test] - fn test_filetransfer_ftp_recv() { - let mut ftp: FtpFileTransfer = FtpFileTransfer::new(false); - // Connect - assert!(ftp.connect(String::from("test.rebex.net"), 21, Some(String::from("demo")), Some(String::from("password"))).is_ok()); - // Pwd - assert_eq!(ftp.pwd().ok().unwrap(), PathBuf::from("/")); - // Recv 100KB - assert!(ftp.recv_file(PathBuf::from("readme.txt").as_path()).is_ok()); - // Disconnect - assert!(ftp.disconnect().is_ok()); - } - - #[test] - fn test_filetransfer_ftp_send() { - let mut ftp: FtpFileTransfer = FtpFileTransfer::new(false); - // Connect - assert!(ftp.connect(String::from("speedtest.tele2.net"), 21, None, None).is_ok()); - // Pwd - assert_eq!(ftp.pwd().ok().unwrap(), PathBuf::from("/")); - // Cwd - assert!(ftp.change_dir(PathBuf::from("upload/").as_path()).is_ok()); - // Pwd - assert_eq!(ftp.pwd().ok().unwrap(), PathBuf::from("/upload")); - // Send a sample file 100KB - assert!(ftp.send_file(PathBuf::from("test.txt").as_path()).is_ok()); - // Disconnect - assert!(ftp.disconnect().is_ok()); - }*/ - - #[test] - fn test_filetransfer_ftp_exec() { - let mut ftp: FtpFileTransfer = FtpFileTransfer::new(false); - // Connect - assert!(ftp - .connect(String::from("speedtest.tele2.net"), 21, None, None) - .is_ok()); - // Pwd - assert!(ftp.exec("echo 1;").is_err()); - // Disconnect - assert!(ftp.disconnect().is_ok()); - } - - #[test] - fn test_filetransfer_ftp_find() { - let mut client: FtpFileTransfer = FtpFileTransfer::new(false); - // Connect - assert!(client - .connect( - String::from("test.rebex.net"), - 21, - Some(String::from("demo")), - Some(String::from("password")) - ) - .is_ok()); - // Pwd - assert_eq!(client.pwd().ok().unwrap(), PathBuf::from("/")); - // Search for file (let's search for pop3-*.png); there should be 2 - let search_res: Vec = client.find("pop3-*.png").ok().unwrap(); - assert_eq!(search_res.len(), 2); - // verify names - assert_eq!(search_res[0].get_name(), "pop3-browser.png"); - assert_eq!(search_res[1].get_name(), "pop3-console-client.png"); - // Search directory - let search_res: Vec = client.find("pub").ok().unwrap(); - assert_eq!(search_res.len(), 1); - // Disconnect - assert!(client.disconnect().is_ok()); - // Verify err - assert!(client.find("pippo").is_err()); + fn test_filetransfer_ftp_get_name_and_link() { + let client: FtpFileTransfer = FtpFileTransfer::new(false); + assert_eq!( + client.get_name_and_link("Cargo.toml"), + (String::from("Cargo.toml"), None) + ); + assert_eq!( + client.get_name_and_link("Cargo -> Cargo.toml"), + (String::from("Cargo"), Some(PathBuf::from("Cargo.toml"))) + ); } #[test] @@ -1316,9 +1306,25 @@ mod tests { assert!(ftp.disconnect().is_err()); assert!(ftp.list_dir(Path::new("/tmp")).is_err()); assert!(ftp.mkdir(Path::new("/tmp")).is_err()); + assert!(ftp + .remove(&make_fsentry(PathBuf::from("/nowhere"), false)) + .is_err()); + assert!(ftp + .rename( + &make_fsentry(PathBuf::from("/nowhere"), false), + PathBuf::from("/culonia").as_path() + ) + .is_err()); assert!(ftp.pwd().is_err()); assert!(ftp.stat(Path::new("/tmp")).is_err()); assert!(ftp.recv_file(&file).is_err()); assert!(ftp.send_file(&file, Path::new("/tmp/omar.txt")).is_err()); + let (_, temp): (FsFile, tempfile::NamedTempFile) = create_sample_file_entry(); + let readable: Box = Box::new(std::fs::File::open(temp.path()).unwrap()); + assert!(ftp.on_recv(readable).is_err()); + let (_, temp): (FsFile, tempfile::NamedTempFile) = create_sample_file_entry(); + let writable: Box = + Box::new(open_file(temp.path(), true, true, true).ok().unwrap()); + assert!(ftp.on_sent(writable).is_err()); } } diff --git a/src/filetransfer/mod.rs b/src/filetransfer/mod.rs index fff23c4..0158e10 100644 --- a/src/filetransfer/mod.rs +++ b/src/filetransfer/mod.rs @@ -284,10 +284,7 @@ pub trait FileTransfer { if filter.matches(dir.name.as_str()) { drained.push(FsEntry::Directory(dir.clone())); } - match self.iter_search(dir.abs_path.as_path(), filter) { - Ok(mut filtered) => drained.append(&mut filtered), - Err(err) => return Err(err), - } + drained.append(&mut self.iter_search(dir.abs_path.as_path(), filter)?); } FsEntry::File(file) => { if filter.matches(file.name.as_str()) { diff --git a/src/filetransfer/scp_transfer.rs b/src/filetransfer/scp_transfer.rs index df17b47..ee994cf 100644 --- a/src/filetransfer/scp_transfer.rs +++ b/src/filetransfer/scp_transfer.rs @@ -445,10 +445,9 @@ impl FileTransfer for ScpFileTransfer { self.session = Some(session); // Get working directory debug!("Getting working directory..."); - match self.perform_shell_cmd("pwd") { - Ok(output) => self.wrkdir = PathBuf::from(output.as_str().trim()), - Err(err) => return Err(err), - } + self.wrkdir = self + .perform_shell_cmd("pwd") + .map(|x| PathBuf::from(x.as_str().trim()))?; info!( "Connection established; working directory: {}", self.wrkdir.display() @@ -486,7 +485,7 @@ impl FileTransfer for ScpFileTransfer { /// /// Indicates whether the client is connected to remote fn is_connected(&self) -> bool { - self.session.as_ref().is_some() + self.session.is_some() } /// ### pwd @@ -853,7 +852,15 @@ impl FileTransfer for ScpFileTransfer { ) -> Result, FileTransferError> { match self.session.as_ref() { Some(session) => { - let file_name: PathBuf = Self::resolve(file_name); + let file_name: PathBuf = match file_name.is_absolute() { + true => PathBuf::from(file_name), + false => { + let mut p: PathBuf = self.wrkdir.clone(); + p.push(file_name); + Self::resolve(p.as_path()) + } + }; + let file_name: PathBuf = Self::resolve(file_name.as_path()); info!( "Sending file {} to {}", local.abs_path.display(), @@ -963,8 +970,12 @@ impl FileTransfer for ScpFileTransfer { mod tests { use super::*; + use crate::utils::test_helpers::make_fsentry; use pretty_assertions::assert_eq; + #[cfg(feature = "with-containers")] + use crate::utils::test_helpers::{create_sample_file_entry, write_file, write_ssh_key}; + #[test] fn test_filetransfer_scp_new() { let client: ScpFileTransfer = ScpFileTransfer::new(SshKeyStorage::empty()); @@ -973,31 +984,210 @@ mod tests { } #[test] - fn test_filetransfer_scp_connect() { + #[cfg(feature = "with-containers")] + fn test_filetransfer_scp_server() { let mut client: ScpFileTransfer = ScpFileTransfer::new(SshKeyStorage::empty()); - assert_eq!(client.is_connected(), false); + // Sample file + let (entry, file): (FsFile, tempfile::NamedTempFile) = create_sample_file_entry(); + // Connect assert!(client .connect( - String::from("test.rebex.net"), - 22, - Some(String::from("demo")), + String::from("127.0.0.1"), + 10222, + Some(String::from("sftp")), Some(String::from("password")) ) .is_ok()); - // Check session and scp + // Check session and sftp assert!(client.session.is_some()); + assert_eq!(client.wrkdir, PathBuf::from("/config")); assert_eq!(client.is_connected(), true); + // Pwd + assert_eq!(client.wrkdir.clone(), client.pwd().ok().unwrap()); + // Stat + let stat: FsFile = client + .stat(PathBuf::from("sshd.pid").as_path()) + .ok() + .unwrap() + .unwrap_file(); + assert_eq!(stat.abs_path, PathBuf::from("/config/sshd.pid")); + let stat: FsDirectory = client + .stat(PathBuf::from("/config/").as_path()) + .ok() + .unwrap() + .unwrap_dir(); + assert_eq!(stat.abs_path, PathBuf::from("/config/")); + // Stat (err) + assert!(client + .stat(PathBuf::from("/config/5t0ca220.log").as_path()) + .is_err()); + // List dir (dir has 4 (one is hidden :D) entries) + assert_eq!(client.list_dir(&Path::new("/config")).unwrap().len(), 4); + // Make directory + assert!(client.mkdir(PathBuf::from("/tmp/omar").as_path()).is_ok()); + // Make directory (err) + assert!(client + .mkdir(PathBuf::from("/root/aaaaa/pommlar").as_path()) + .is_err()); + // Change directory + assert!(client + .change_dir(PathBuf::from("/tmp/omar").as_path()) + .is_ok()); + // Change directory (err) + assert!(client + .change_dir(PathBuf::from("/tmp/oooo/aaaa/eee").as_path()) + .is_err()); + // Copy file + assert!(client + .copy( + &make_fsentry(PathBuf::from("/config/sshd.pid"), false), + PathBuf::from("/tmp/sshd.pid").as_path() + ) + .is_ok()); + // Copy dir + assert!(client + .copy( + &make_fsentry(PathBuf::from("/tmp/omar"), true), + PathBuf::from("/tmp/ommlar").as_path() + ) + .is_ok()); + // Copy (err) + assert!(client + .copy( + &make_fsentry(PathBuf::from("/tmp/zattera"), false), + PathBuf::from("/").as_path() + ) + .is_err()); + // Exec + assert_eq!(client.exec("echo 5").ok().unwrap().as_str(), "5\n"); + // Change dir to ommlar + assert!(client + .change_dir(PathBuf::from("/tmp/ommlar/").as_path()) + .is_ok()); + // Upload 2 files + let mut writable = client + .send_file(&entry, PathBuf::from("omar.txt").as_path()) + .ok() + .unwrap(); + write_file(&file, &mut writable); + assert!(client.on_sent(writable).is_ok()); + let mut writable = client + .send_file(&entry, PathBuf::from("README.md").as_path()) + .ok() + .unwrap(); + write_file(&file, &mut writable); + assert!(client.on_sent(writable).is_ok()); + // Upload file (err) + assert!(client + .send_file(&entry, PathBuf::from("/ommlar/omarone").as_path()) + .is_err()); + // List dir + let list: Vec = client + .list_dir(PathBuf::from("/tmp/ommlar").as_path()) + .ok() + .unwrap(); + assert_eq!(list.len(), 2); + // Find + assert_eq!(client.find("*.txt").ok().unwrap().len(), 1); + assert_eq!(client.find("*.md").ok().unwrap().len(), 1); + assert_eq!(client.find("*.jpeg").ok().unwrap().len(), 0); + // Rename + assert!(client + .mkdir(PathBuf::from("/tmp/uploads").as_path()) + .is_ok()); + assert!(client + .rename( + list.get(0).unwrap(), + PathBuf::from("/tmp/uploads/README.txt").as_path() + ) + .is_ok()); + // Rename (err) + assert!(client + .rename(list.get(0).unwrap(), PathBuf::from("OMARONE").as_path()) + .is_err()); + let dummy: FsEntry = FsEntry::File(FsFile { + name: String::from("cucumber.txt"), + abs_path: PathBuf::from("/cucumber.txt"), + last_change_time: SystemTime::UNIX_EPOCH, + last_access_time: SystemTime::UNIX_EPOCH, + creation_time: SystemTime::UNIX_EPOCH, + size: 0, + ftype: Some(String::from("txt")), // File type + readonly: true, + symlink: None, // UNIX only + user: Some(0), // UNIX only + group: Some(0), // UNIX only + unix_pex: Some((6, 4, 4)), // UNIX only + }); + assert!(client + .rename(&dummy, PathBuf::from("/a/b/c").as_path()) + .is_err()); + // Remove + assert!(client.remove(list.get(1).unwrap()).is_ok()); + // Receive file + let mut writable = client + .send_file(&entry, PathBuf::from("/tmp/uploads/README.txt").as_path()) + .ok() + .unwrap(); + write_file(&file, &mut writable); + assert!(client.on_sent(writable).is_ok()); + let file: FsFile = client + .list_dir(PathBuf::from("/tmp/uploads").as_path()) + .ok() + .unwrap() + .get(0) + .unwrap() + .clone() + .unwrap_file(); + let mut readable = client.recv_file(&file).ok().unwrap(); + let mut data: Vec = vec![0; 1024]; + assert!(readable.read(&mut data).is_ok()); + assert!(client.on_recv(readable).is_ok()); + // Receive file (err) + assert!(client.recv_file(&entry).is_err()); + // Cleanup + assert!(client.change_dir(PathBuf::from("/").as_path()).is_ok()); + assert!(client + .remove(&make_fsentry(PathBuf::from("/tmp/ommlar"), true)) + .is_ok()); + assert!(client + .remove(&make_fsentry(PathBuf::from("/tmp/omar"), true)) + .is_ok()); + assert!(client + .remove(&make_fsentry(PathBuf::from("/tmp/uploads"), true)) + .is_ok()); // Disconnect assert!(client.disconnect().is_ok()); assert_eq!(client.is_connected(), false); } + + #[test] + #[cfg(feature = "with-containers")] + fn test_filetransfer_scp_ssh_storage() { + let mut storage: SshKeyStorage = SshKeyStorage::empty(); + let key_file: tempfile::NamedTempFile = write_ssh_key(); + storage.add_key("127.0.0.1", "sftp", key_file.path().to_path_buf()); + let mut client: ScpFileTransfer = ScpFileTransfer::new(storage); + // Connect + assert!(client + .connect( + String::from("127.0.0.1"), + 10222, + Some(String::from("sftp")), + None, + ) + .is_ok()); + assert_eq!(client.is_connected(), true); + assert!(client.disconnect().is_ok()); + } + #[test] fn test_filetransfer_scp_bad_auth() { let mut client: ScpFileTransfer = ScpFileTransfer::new(SshKeyStorage::empty()); assert!(client .connect( - String::from("test.rebex.net"), - 22, + String::from("127.0.0.1"), + 10222, Some(String::from("demo")), Some(String::from("badpassword")) ) @@ -1005,10 +1195,11 @@ mod tests { } #[test] + #[cfg(feature = "with-containers")] fn test_filetransfer_scp_no_credentials() { let mut client: ScpFileTransfer = ScpFileTransfer::new(SshKeyStorage::empty()); assert!(client - .connect(String::from("test.rebex.net"), 22, None, None) + .connect(String::from("127.0.0.1"), 10222, None, None) .is_err()); } @@ -1024,245 +1215,92 @@ mod tests { ) .is_err()); } - #[test] - fn test_filetransfer_scp_pwd() { - let mut client: ScpFileTransfer = ScpFileTransfer::new(SshKeyStorage::empty()); - assert!(client - .connect( - String::from("test.rebex.net"), - 22, - Some(String::from("demo")), - Some(String::from("password")) - ) - .is_ok()); - // Check session and scp - assert!(client.session.is_some()); - // Pwd - assert_eq!(client.pwd().ok().unwrap(), PathBuf::from("/")); - // Disconnect - assert!(client.disconnect().is_ok()); - } #[test] - #[cfg(any(target_os = "unix", target_os = "macos", target_os = "linux"))] - fn test_filetransfer_scp_cwd() { + fn test_filetransfer_scp_parse_ls() { let mut client: ScpFileTransfer = ScpFileTransfer::new(SshKeyStorage::empty()); - assert!(client - .connect( - String::from("test.rebex.net"), - 22, - Some(String::from("demo")), - Some(String::from("password")) + // File + let entry: FsFile = client + .parse_ls_output( + PathBuf::from("/tmp").as_path(), + "-rw-r--r-- 1 root root 2056 giu 13 21:11 Cargo.toml", ) - .is_ok()); - // Check session and scp - assert!(client.session.is_some()); - // Cwd (relative) - assert!(client.change_dir(PathBuf::from("pub/").as_path()).is_ok()); - // Cwd (absolute) - assert!(client.change_dir(PathBuf::from("/pub").as_path()).is_ok()); - // Disconnect - assert!(client.disconnect().is_ok()); - } - - #[test] - fn test_filetransfer_scp_cwd_error() { - let mut client: ScpFileTransfer = ScpFileTransfer::new(SshKeyStorage::empty()); - assert!(client - .connect( - String::from("test.rebex.net"), - 22, - Some(String::from("demo")), - Some(String::from("password")) - ) - .is_ok()); - // Cwd (abs) - assert!(client - .change_dir(PathBuf::from("/omar/gabber").as_path()) - .is_err()); - // Cwd (rel) - assert!(client - .change_dir(PathBuf::from("gomar/pett").as_path()) - .is_err()); - // Disconnect - assert!(client.disconnect().is_ok()); - } - - #[test] - fn test_filetransfer_scp_ls() { - let mut client: ScpFileTransfer = ScpFileTransfer::new(SshKeyStorage::empty()); - assert!(client - .connect( - String::from("test.rebex.net"), - 22, - Some(String::from("demo")), - Some(String::from("password")) - ) - .is_ok()); - // Check session and scp - assert!(client.session.is_some()); - // List dir - let pwd: PathBuf = client.pwd().ok().unwrap(); - let files: Vec = client.list_dir(pwd.as_path()).ok().unwrap(); - assert_eq!(files.len(), 3); // There are 3 files - // Disconnect - assert!(client.disconnect().is_ok()); - } - - #[test] - fn test_filetransfer_scp_stat() { - let mut client: ScpFileTransfer = ScpFileTransfer::new(SshKeyStorage::empty()); - assert!(client - .connect( - String::from("test.rebex.net"), - 22, - Some(String::from("demo")), - Some(String::from("password")) - ) - .is_ok()); - // Check session and scp - assert!(client.session.is_some()); - let file: FsEntry = client - .stat(PathBuf::from("readme.txt").as_path()) .ok() - .unwrap(); - if let FsEntry::File(file) = file { - assert_eq!(file.abs_path, PathBuf::from("/readme.txt")); - } else { - panic!("Expected readme.txt to be a file"); - } + .unwrap() + .unwrap_file(); + assert_eq!(entry.name.as_str(), "Cargo.toml"); + assert_eq!(entry.abs_path, PathBuf::from("/tmp/Cargo.toml")); + assert_eq!(entry.unix_pex.unwrap(), (6, 4, 4)); + assert_eq!(entry.size, 2056); + assert_eq!(entry.readonly, false); + assert_eq!(entry.ftype.unwrap().as_str(), "toml"); + assert!(entry.symlink.is_none()); + // File (year) + let entry: FsFile = client + .parse_ls_output( + PathBuf::from("/tmp").as_path(), + "-rw-rw-rw- 1 root root 3368 nov 7 2020 CODE_OF_CONDUCT.md", + ) + .ok() + .unwrap() + .unwrap_file(); + assert_eq!(entry.name.as_str(), "CODE_OF_CONDUCT.md"); + assert_eq!(entry.abs_path, PathBuf::from("/tmp/CODE_OF_CONDUCT.md")); + assert_eq!(entry.unix_pex.unwrap(), (6, 6, 6)); + assert_eq!(entry.size, 3368); + assert_eq!(entry.readonly, false); + assert_eq!(entry.ftype.unwrap().as_str(), "md"); + assert!(entry.symlink.is_none()); + // Directory + let entry: FsDirectory = client + .parse_ls_output( + PathBuf::from("/tmp").as_path(), + "drwxr-xr-x 1 root root 512 giu 13 21:11 docs", + ) + .ok() + .unwrap() + .unwrap_dir(); + assert_eq!(entry.name.as_str(), "docs"); + assert_eq!(entry.abs_path, PathBuf::from("/tmp/docs")); + assert_eq!(entry.unix_pex.unwrap(), (7, 5, 5)); + assert_eq!(entry.readonly, false); + assert!(entry.symlink.is_none()); + // Short metadata + assert!(client + .parse_ls_output( + PathBuf::from("/tmp").as_path(), + "drwxr-xr-x 1 root root 512 giu 13 21:11", + ) + .is_err()); + // Special file + assert!(client + .parse_ls_output( + PathBuf::from("/tmp").as_path(), + "crwxr-xr-x 1 root root 512 giu 13 21:11 ttyS1", + ) + .is_err()); + // Bad pex + assert!(client + .parse_ls_output( + PathBuf::from("/tmp").as_path(), + "-rwxr-xr 1 root root 512 giu 13 21:11 ttyS1", + ) + .is_err()); } #[test] - fn test_filetransfer_scp_exec() { - let mut client: ScpFileTransfer = ScpFileTransfer::new(SshKeyStorage::empty()); - assert!(client - .connect( - String::from("test.rebex.net"), - 22, - Some(String::from("demo")), - Some(String::from("password")) - ) - .is_ok()); - // Check session and scp - assert!(client.session.is_some()); - // Exec - assert_eq!(client.exec("echo 5").ok().unwrap().as_str(), "5\n"); - // Disconnect - assert!(client.disconnect().is_ok()); + fn test_filetransfer_scp_get_name_and_link() { + let client: ScpFileTransfer = ScpFileTransfer::new(SshKeyStorage::empty()); + assert_eq!( + client.get_name_and_link("Cargo.toml"), + (String::from("Cargo.toml"), None) + ); + assert_eq!( + client.get_name_and_link("Cargo -> Cargo.toml"), + (String::from("Cargo"), Some(PathBuf::from("Cargo.toml"))) + ); } - #[test] - //#[cfg(any(target_os = "unix", target_os = "macos", target_os = "linux"))] - fn test_filetransfer_scp_find() { - let mut client: ScpFileTransfer = ScpFileTransfer::new(SshKeyStorage::empty()); - assert!(client - .connect( - String::from("test.rebex.net"), - 22, - Some(String::from("demo")), - Some(String::from("password")) - ) - .is_ok()); - // Check session and scp - assert!(client.session.is_some()); - // Search for file (let's search for pop3-*.png); there should be 2 - let search_res: Vec = client.find("pop3-*.png").ok().unwrap(); - assert_eq!(search_res.len(), 2); - // verify names - assert_eq!(search_res[0].get_name(), "pop3-browser.png"); - assert_eq!(search_res[1].get_name(), "pop3-console-client.png"); - // Search directory - let search_res: Vec = client.find("pub").ok().unwrap(); - assert_eq!(search_res.len(), 1); - // Disconnect - assert!(client.disconnect().is_ok()); - // Verify err - assert!(client.find("pippo").is_err()); - } - - #[test] - fn test_filetransfer_scp_recv() { - let mut client: ScpFileTransfer = ScpFileTransfer::new(SshKeyStorage::empty()); - assert!(client - .connect( - String::from("test.rebex.net"), - 22, - Some(String::from("demo")), - Some(String::from("password")) - ) - .is_ok()); - // Check session and scp - assert!(client.session.is_some()); - let file: FsFile = FsFile { - name: String::from("readme.txt"), - abs_path: PathBuf::from("/readme.txt"), - last_change_time: SystemTime::UNIX_EPOCH, - last_access_time: SystemTime::UNIX_EPOCH, - creation_time: SystemTime::UNIX_EPOCH, - size: 0, - ftype: Some(String::from("txt")), // File type - readonly: true, - symlink: None, // UNIX only - user: Some(0), // UNIX only - group: Some(0), // UNIX only - unix_pex: Some((6, 4, 4)), // UNIX only - }; - // Receive file - assert!(client.recv_file(&file).is_ok()); - // Disconnect - assert!(client.disconnect().is_ok()); - } - #[test] - fn test_filetransfer_scp_recv_failed_nosuchfile() { - let mut client: ScpFileTransfer = ScpFileTransfer::new(SshKeyStorage::empty()); - assert!(client - .connect( - String::from("test.rebex.net"), - 22, - Some(String::from("demo")), - Some(String::from("password")) - ) - .is_ok()); - // Check session and scp - assert!(client.session.is_some()); - // Receive file - let file: FsFile = FsFile { - name: String::from("omar.txt"), - abs_path: PathBuf::from("/omar.txt"), - last_change_time: SystemTime::UNIX_EPOCH, - last_access_time: SystemTime::UNIX_EPOCH, - creation_time: SystemTime::UNIX_EPOCH, - size: 0, - ftype: Some(String::from("txt")), // File type - readonly: true, - symlink: None, // UNIX only - user: Some(0), // UNIX only - group: Some(0), // UNIX only - unix_pex: Some((6, 4, 4)), // UNIX only - }; - assert!(client.recv_file(&file).is_err()); - // Disconnect - assert!(client.disconnect().is_ok()); - } - // NOTE: other functions doesn't work with this test scp server - - /* NOTE: the server doesn't allow you to create directories - #[test] - fn test_filetransfer_scp_mkdir() { - let mut client: ScpFileTransfer = ScpFileTransfer::new(SshKeyStorage::empty()); - assert!(client.connect(String::from("test.rebex.net"), 22, Some(String::from("demo")), Some(String::from("password"))).is_ok()); - let dir: String = String::from("foo"); - // Mkdir - assert!(client.mkdir(dir).is_ok()); - // cwd - assert!(client.change_dir(PathBuf::from("foo/").as_path()).is_ok()); - assert_eq!(client.wrkdir, PathBuf::from("/foo")); - // Disconnect - assert!(client.disconnect().is_ok()); - } - */ - #[test] fn test_filetransfer_scp_uninitialized() { let file: FsFile = FsFile { @@ -1282,9 +1320,19 @@ mod tests { let mut scp: ScpFileTransfer = ScpFileTransfer::new(SshKeyStorage::empty()); assert!(scp.change_dir(Path::new("/tmp")).is_err()); assert!(scp.disconnect().is_err()); + assert!(scp.exec("echo 5").is_err()); assert!(scp.list_dir(Path::new("/tmp")).is_err()); assert!(scp.mkdir(Path::new("/tmp")).is_err()); assert!(scp.pwd().is_err()); + assert!(scp + .remove(&make_fsentry(PathBuf::from("/nowhere"), false)) + .is_err()); + assert!(scp + .rename( + &make_fsentry(PathBuf::from("/nowhere"), false), + PathBuf::from("/culonia").as_path() + ) + .is_err()); assert!(scp.stat(Path::new("/tmp")).is_err()); assert!(scp.recv_file(&file).is_err()); assert!(scp.send_file(&file, Path::new("/tmp/omar.txt")).is_err()); diff --git a/src/filetransfer/sftp_transfer.rs b/src/filetransfer/sftp_transfer.rs index d63f1c4..0712661 100644 --- a/src/filetransfer/sftp_transfer.rs +++ b/src/filetransfer/sftp_transfer.rs @@ -466,10 +466,7 @@ impl FileTransfer for SftpFileTransfer { match self.sftp.as_ref() { Some(_) => { // Change working directory - self.wrkdir = match self.get_remote_path(dir) { - Ok(p) => p, - Err(err) => return Err(err), - }; + self.wrkdir = self.get_remote_path(dir)?; info!("Changed working directory to {}", self.wrkdir.display()); Ok(self.wrkdir.clone()) } @@ -532,10 +529,7 @@ impl FileTransfer for SftpFileTransfer { match self.sftp.as_ref() { Some(sftp) => { // Get path - let dir: PathBuf = match self.get_remote_path(path) { - Ok(p) => p, - Err(err) => return Err(err), - }; + let dir: PathBuf = self.get_remote_path(path)?; info!("Getting file entries in {}", path.display()); // Get files match sftp.readdir(dir.as_path()) { @@ -609,10 +603,7 @@ impl FileTransfer for SftpFileTransfer { // Remove recursively debug!("{} is a directory; removing all directory entries", d.name); // Get directory files - let directory_content: Vec = match self.list_dir(d.abs_path.as_path()) { - Ok(entries) => entries, - Err(err) => return Err(err), - }; + let directory_content: Vec = self.list_dir(d.abs_path.as_path())?; for entry in directory_content.iter() { if let Err(err) = self.remove(&entry) { return Err(err); @@ -666,10 +657,7 @@ impl FileTransfer for SftpFileTransfer { match self.sftp.as_ref() { Some(sftp) => { // Get path - let dir: PathBuf = match self.get_remote_path(path) { - Ok(p) => p, - Err(err) => return Err(err), - }; + let dir: PathBuf = self.get_remote_path(path)?; info!("Stat file {}", dir.display()); // Get file match sftp.stat(dir.as_path()) { @@ -758,10 +746,7 @@ impl FileTransfer for SftpFileTransfer { )), Some(sftp) => { // Get remote file name - let remote_path: PathBuf = match self.get_remote_path(file.abs_path.as_path()) { - Ok(p) => p, - Err(err) => return Err(err), - }; + let remote_path: PathBuf = self.get_remote_path(file.abs_path.as_path())?; info!("Receiving file {}", remote_path.display()); // Open remote file match sftp.open(remote_path.as_path()) { @@ -800,7 +785,9 @@ impl FileTransfer for SftpFileTransfer { mod tests { use super::*; - + use crate::utils::test_helpers::make_fsentry; + #[cfg(feature = "with-containers")] + use crate::utils::test_helpers::{create_sample_file_entry, write_file, write_ssh_key}; use pretty_assertions::assert_eq; #[test] @@ -813,34 +800,188 @@ mod tests { } #[test] - fn test_filetransfer_sftp_connect() { + #[cfg(feature = "with-containers")] + fn test_filetransfer_sftp_server() { let mut client: SftpFileTransfer = SftpFileTransfer::new(SshKeyStorage::empty()); - assert_eq!(client.is_connected(), false); + // Sample file + let (entry, file): (FsFile, tempfile::NamedTempFile) = create_sample_file_entry(); + // Connect assert!(client .connect( - String::from("test.rebex.net"), - 22, - Some(String::from("demo")), + String::from("127.0.0.1"), + 10022, + Some(String::from("sftp")), Some(String::from("password")) ) .is_ok()); // Check session and sftp assert!(client.session.is_some()); assert!(client.sftp.is_some()); - assert_eq!(client.wrkdir, PathBuf::from("/")); + assert_eq!(client.wrkdir, PathBuf::from("/config")); assert_eq!(client.is_connected(), true); + // Pwd + assert_eq!(client.wrkdir.clone(), client.pwd().ok().unwrap()); + // Stat + let stat: FsFile = client + .stat(PathBuf::from("/config/sshd.pid").as_path()) + .ok() + .unwrap() + .unwrap_file(); + assert_eq!(stat.name.as_str(), "sshd.pid"); + let stat: FsDirectory = client + .stat(PathBuf::from("/config").as_path()) + .ok() + .unwrap() + .unwrap_dir(); + assert_eq!(stat.name.as_str(), "config"); + // Stat (err) + assert!(client + .stat(PathBuf::from("/config/5t0ca220.log").as_path()) + .is_err()); + // List dir (dir has 4 (one is hidden :D) entries) + assert_eq!(client.list_dir(&Path::new("/config")).unwrap().len(), 4); + // Make directory + assert!(client.mkdir(PathBuf::from("/tmp/omar").as_path()).is_ok()); + // Make directory (err) + assert!(client + .mkdir(PathBuf::from("/root/aaaaa/pommlar").as_path()) + .is_err()); + // Change directory + assert!(client + .change_dir(PathBuf::from("/tmp/omar").as_path()) + .is_ok()); + // Change directory (err) + assert!(client + .change_dir(PathBuf::from("/tmp/oooo/aaaa/eee").as_path()) + .is_err()); + // Copy (not supported) + assert!(client + .copy(&FsEntry::File(entry.clone()), PathBuf::from("/").as_path()) + .is_err()); + // Exec + assert_eq!(client.exec("echo 5").ok().unwrap().as_str(), "5\n"); + // Upload 2 files + let mut writable = client + .send_file(&entry, PathBuf::from("omar.txt").as_path()) + .ok() + .unwrap(); + write_file(&file, &mut writable); + assert!(client.on_sent(writable).is_ok()); + let mut writable = client + .send_file(&entry, PathBuf::from("README.md").as_path()) + .ok() + .unwrap(); + write_file(&file, &mut writable); + assert!(client.on_sent(writable).is_ok()); + // Upload file (err) + assert!(client + .send_file(&entry, PathBuf::from("/ommlar/omarone").as_path()) + .is_err()); + // List dir + let list: Vec = client + .list_dir(PathBuf::from("/tmp/omar").as_path()) + .ok() + .unwrap(); + assert_eq!(list.len(), 2); + // Find + assert_eq!(client.find("*.txt").ok().unwrap().len(), 1); + assert_eq!(client.find("*.md").ok().unwrap().len(), 1); + assert_eq!(client.find("*.jpeg").ok().unwrap().len(), 0); + // Rename + assert!(client + .mkdir(PathBuf::from("/tmp/uploads").as_path()) + .is_ok()); + assert!(client + .rename( + list.get(0).unwrap(), + PathBuf::from("/tmp/uploads/README.txt").as_path() + ) + .is_ok()); + // Rename (err) + assert!(client + .rename(list.get(0).unwrap(), PathBuf::from("OMARONE").as_path()) + .is_err()); + let dummy: FsEntry = FsEntry::File(FsFile { + name: String::from("cucumber.txt"), + abs_path: PathBuf::from("/cucumber.txt"), + last_change_time: SystemTime::UNIX_EPOCH, + last_access_time: SystemTime::UNIX_EPOCH, + creation_time: SystemTime::UNIX_EPOCH, + size: 0, + ftype: Some(String::from("txt")), // File type + readonly: true, + symlink: None, // UNIX only + user: Some(0), // UNIX only + group: Some(0), // UNIX only + unix_pex: Some((6, 4, 4)), // UNIX only + }); + assert!(client + .rename(&dummy, PathBuf::from("/a/b/c").as_path()) + .is_err()); + // Remove + assert!(client.remove(list.get(1).unwrap()).is_ok()); + assert!(client.remove(list.get(1).unwrap()).is_err()); + // Receive file + let mut writable = client + .send_file(&entry, PathBuf::from("/tmp/uploads/README.txt").as_path()) + .ok() + .unwrap(); + write_file(&file, &mut writable); + assert!(client.on_sent(writable).is_ok()); + let file: FsFile = client + .list_dir(PathBuf::from("/tmp/uploads").as_path()) + .ok() + .unwrap() + .get(0) + .unwrap() + .clone() + .unwrap_file(); + let mut readable = client.recv_file(&file).ok().unwrap(); + let mut data: Vec = vec![0; 1024]; + assert!(readable.read(&mut data).is_ok()); + assert!(client.on_recv(readable).is_ok()); + // Receive file (err) + assert!(client.recv_file(&entry).is_err()); + // Cleanup + assert!(client.change_dir(PathBuf::from("/").as_path()).is_ok()); + assert!(client + .remove(&make_fsentry(PathBuf::from("/tmp/omar"), true)) + .is_ok()); + assert!(client + .remove(&make_fsentry(PathBuf::from("/tmp/uploads"), true)) + .is_ok()); // Disconnect assert!(client.disconnect().is_ok()); assert_eq!(client.is_connected(), false); } + #[test] + #[cfg(feature = "with-containers")] + fn test_filetransfer_sftp_ssh_storage() { + let mut storage: SshKeyStorage = SshKeyStorage::empty(); + let key_file: tempfile::NamedTempFile = write_ssh_key(); + storage.add_key("127.0.0.1", "sftp", key_file.path().to_path_buf()); + let mut client: SftpFileTransfer = SftpFileTransfer::new(storage); + // Connect + assert!(client + .connect( + String::from("127.0.0.1"), + 10022, + Some(String::from("sftp")), + None, + ) + .is_ok()); + assert_eq!(client.is_connected(), true); + assert!(client.disconnect().is_ok()); + } + #[test] fn test_filetransfer_sftp_bad_auth() { let mut client: SftpFileTransfer = SftpFileTransfer::new(SshKeyStorage::empty()); assert!(client .connect( - String::from("test.rebex.net"), - 22, + String::from("127.0.0.1"), + 10022, Some(String::from("demo")), Some(String::from("badpassword")) ) @@ -848,13 +989,52 @@ mod tests { } #[test] + #[cfg(feature = "with-containers")] fn test_filetransfer_sftp_no_credentials() { let mut client: SftpFileTransfer = SftpFileTransfer::new(SshKeyStorage::empty()); assert!(client - .connect(String::from("test.rebex.net"), 22, None, None) + .connect(String::from("127.0.0.1"), 10022, None, None) .is_err()); } + #[test] + #[cfg(feature = "with-containers")] + fn test_filetransfer_sftp_get_remote_path() { + let mut client: SftpFileTransfer = SftpFileTransfer::new(SshKeyStorage::empty()); + // Connect + assert!(client + .connect( + String::from("127.0.0.1"), + 10022, + Some(String::from("sftp")), + Some(String::from("password")) + ) + .is_ok()); + // get realpath + assert!(client + .change_dir(PathBuf::from("/config").as_path()) + .is_ok()); + assert_eq!( + client + .get_remote_path(PathBuf::from("sshd.pid").as_path()) + .ok() + .unwrap(), + PathBuf::from("/config/sshd.pid") + ); + // No such file + assert!(client + .get_remote_path(PathBuf::from("omarone.txt").as_path()) + .is_err()); + // Ok abs path + assert_eq!( + client + .get_remote_path(PathBuf::from("/config/sshd.pid").as_path()) + .ok() + .unwrap(), + PathBuf::from("/config/sshd.pid") + ); + } + #[test] fn test_filetransfer_sftp_bad_server() { let mut client: SftpFileTransfer = SftpFileTransfer::new(SshKeyStorage::empty()); @@ -868,302 +1048,6 @@ mod tests { .is_err()); } - #[test] - fn test_filetransfer_sftp_pwd() { - let mut client: SftpFileTransfer = SftpFileTransfer::new(SshKeyStorage::empty()); - assert!(client - .connect( - String::from("test.rebex.net"), - 22, - Some(String::from("demo")), - Some(String::from("password")) - ) - .is_ok()); - // Check session and sftp - assert!(client.session.is_some()); - assert!(client.sftp.is_some()); - assert_eq!(client.wrkdir, PathBuf::from("/")); - // Pwd - assert_eq!(client.wrkdir.clone(), client.pwd().ok().unwrap()); - // Disconnect - assert!(client.disconnect().is_ok()); - } - - #[test] - fn test_filetransfer_sftp_cwd() { - let mut client: SftpFileTransfer = SftpFileTransfer::new(SshKeyStorage::empty()); - assert!(client - .connect( - String::from("test.rebex.net"), - 22, - Some(String::from("demo")), - Some(String::from("password")) - ) - .is_ok()); - // Check session and sftp - assert!(client.session.is_some()); - assert!(client.sftp.is_some()); - assert_eq!(client.wrkdir, PathBuf::from("/")); - // Pwd - assert_eq!(client.wrkdir.clone(), client.pwd().ok().unwrap()); - // Cwd (relative) - assert!(client.change_dir(PathBuf::from("pub/").as_path()).is_ok()); - assert_eq!(client.wrkdir, PathBuf::from("/pub")); - // Cwd (absolute) - assert!(client.change_dir(PathBuf::from("/").as_path()).is_ok()); - assert_eq!(client.wrkdir, PathBuf::from("/")); - // Disconnect - assert!(client.disconnect().is_ok()); - } - - #[test] - fn test_filetransfer_sftp_copy() { - let mut client: SftpFileTransfer = SftpFileTransfer::new(SshKeyStorage::empty()); - assert!(client - .connect( - String::from("test.rebex.net"), - 22, - Some(String::from("demo")), - Some(String::from("password")) - ) - .is_ok()); - // Check session and sftp - assert!(client.session.is_some()); - assert!(client.sftp.is_some()); - assert_eq!(client.wrkdir, PathBuf::from("/")); - // Copy - let file: FsFile = FsFile { - name: String::from("readme.txt"), - abs_path: PathBuf::from("/readme.txt"), - last_change_time: SystemTime::UNIX_EPOCH, - last_access_time: SystemTime::UNIX_EPOCH, - creation_time: SystemTime::UNIX_EPOCH, - size: 0, - ftype: Some(String::from("txt")), // File type - readonly: true, - symlink: None, // UNIX only - user: Some(0), // UNIX only - group: Some(0), // UNIX only - unix_pex: Some((6, 4, 4)), // UNIX only - }; - assert!(client - .copy(&FsEntry::File(file), &Path::new("/tmp/dest.txt")) - .is_err()); - } - - #[test] - fn test_filetransfer_sftp_cwd_error() { - let mut client: SftpFileTransfer = SftpFileTransfer::new(SshKeyStorage::empty()); - assert!(client - .connect( - String::from("test.rebex.net"), - 22, - Some(String::from("demo")), - Some(String::from("password")) - ) - .is_ok()); - // Cwd (abs) - assert!(client - .change_dir(PathBuf::from("/omar/gabber").as_path()) - .is_err()); - // Cwd (rel) - assert!(client - .change_dir(PathBuf::from("gomar/pett").as_path()) - .is_err()); - // Disconnect - assert!(client.disconnect().is_ok()); - assert!(client - .change_dir(PathBuf::from("gomar/pett").as_path()) - .is_err()); - } - - #[test] - fn test_filetransfer_sftp_ls() { - let mut client: SftpFileTransfer = SftpFileTransfer::new(SshKeyStorage::empty()); - assert!(client - .connect( - String::from("test.rebex.net"), - 22, - Some(String::from("demo")), - Some(String::from("password")) - ) - .is_ok()); - // Check session and sftp - assert!(client.session.is_some()); - assert!(client.sftp.is_some()); - assert_eq!(client.wrkdir, PathBuf::from("/")); - // List dir - let pwd: PathBuf = client.pwd().ok().unwrap(); - let files: Vec = client.list_dir(pwd.as_path()).ok().unwrap(); - assert_eq!(files.len(), 3); // There are 3 files - // Disconnect - assert!(client.disconnect().is_ok()); - // Verify err - assert!(client.list_dir(pwd.as_path()).is_err()); - } - - #[test] - fn test_filetransfer_sftp_stat() { - let mut client: SftpFileTransfer = SftpFileTransfer::new(SshKeyStorage::empty()); - assert!(client - .connect( - String::from("test.rebex.net"), - 22, - Some(String::from("demo")), - Some(String::from("password")) - ) - .is_ok()); - // Check session and sftp - assert!(client.session.is_some()); - assert!(client.sftp.is_some()); - assert_eq!(client.wrkdir, PathBuf::from("/")); - let file: FsEntry = client - .stat(PathBuf::from("readme.txt").as_path()) - .ok() - .unwrap(); - if let FsEntry::File(file) = file { - assert_eq!(file.abs_path, PathBuf::from("/readme.txt")); - } else { - panic!("Expected readme.txt to be a file"); - } - } - - #[test] - fn test_filetransfer_sftp_exec() { - let mut client: SftpFileTransfer = SftpFileTransfer::new(SshKeyStorage::empty()); - assert!(client - .connect( - String::from("test.rebex.net"), - 22, - Some(String::from("demo")), - Some(String::from("password")) - ) - .is_ok()); - // Check session and scp - assert!(client.session.is_some()); - // Exec - assert_eq!(client.exec("echo 5").ok().unwrap().as_str(), "5\n"); - // Disconnect - assert!(client.disconnect().is_ok()); - // Verify err - assert!(client.exec("echo 1").is_err()); - } - - #[test] - fn test_filetransfer_sftp_find() { - let mut client: SftpFileTransfer = SftpFileTransfer::new(SshKeyStorage::empty()); - assert!(client - .connect( - String::from("test.rebex.net"), - 22, - Some(String::from("demo")), - Some(String::from("password")) - ) - .is_ok()); - // Check session and scp - assert!(client.session.is_some()); - // Search for file (let's search for pop3-*.png); there should be 2 - let search_res: Vec = client.find("pop3-*.png").ok().unwrap(); - assert_eq!(search_res.len(), 2); - // verify names - assert_eq!(search_res[0].get_name(), "pop3-browser.png"); - assert_eq!(search_res[1].get_name(), "pop3-console-client.png"); - // Search directory - let search_res: Vec = client.find("pub").ok().unwrap(); - assert_eq!(search_res.len(), 1); - // Disconnect - assert!(client.disconnect().is_ok()); - // Verify err - assert!(client.find("pippo").is_err()); - } - - #[test] - fn test_filetransfer_sftp_recv() { - let mut client: SftpFileTransfer = SftpFileTransfer::new(SshKeyStorage::empty()); - assert!(client - .connect( - String::from("test.rebex.net"), - 22, - Some(String::from("demo")), - Some(String::from("password")) - ) - .is_ok()); - // Check session and sftp - assert!(client.session.is_some()); - assert!(client.sftp.is_some()); - assert_eq!(client.wrkdir, PathBuf::from("/")); - let file: FsFile = FsFile { - name: String::from("readme.txt"), - abs_path: PathBuf::from("/readme.txt"), - last_change_time: SystemTime::UNIX_EPOCH, - last_access_time: SystemTime::UNIX_EPOCH, - creation_time: SystemTime::UNIX_EPOCH, - size: 0, - ftype: Some(String::from("txt")), // File type - readonly: true, - symlink: None, // UNIX only - user: Some(0), // UNIX only - group: Some(0), // UNIX only - unix_pex: Some((6, 4, 4)), // UNIX only - }; - // Receive file - assert!(client.recv_file(&file).is_ok()); - // Disconnect - assert!(client.disconnect().is_ok()); - } - #[test] - fn test_filetransfer_sftp_recv_failed_nosuchfile() { - let mut client: SftpFileTransfer = SftpFileTransfer::new(SshKeyStorage::empty()); - assert!(client - .connect( - String::from("test.rebex.net"), - 22, - Some(String::from("demo")), - Some(String::from("password")) - ) - .is_ok()); - // Check session and sftp - assert!(client.session.is_some()); - assert!(client.sftp.is_some()); - assert_eq!(client.wrkdir, PathBuf::from("/")); - // Receive file - let file: FsFile = FsFile { - name: String::from("omar.txt"), - abs_path: PathBuf::from("/omar.txt"), - last_change_time: SystemTime::UNIX_EPOCH, - last_access_time: SystemTime::UNIX_EPOCH, - creation_time: SystemTime::UNIX_EPOCH, - size: 0, - ftype: Some(String::from("txt")), // File type - readonly: true, - symlink: None, // UNIX only - user: Some(0), // UNIX only - group: Some(0), // UNIX only - unix_pex: Some((6, 4, 4)), // UNIX only - }; - assert!(client.recv_file(&file).is_err()); - // Disconnect - assert!(client.disconnect().is_ok()); - } - - // NOTE: other functions doesn't work with this test SFTP server - - /* NOTE: the server doesn't allow you to create directories - #[test] - fn test_filetransfer_sftp_mkdir() { - let mut client: SftpFileTransfer = SftpFileTransfer::new(SshKeyStorage::empty()); - assert!(client.connect(String::from("test.rebex.net"), 22, Some(String::from("demo")), Some(String::from("password"))).is_ok()); - let dir: String = String::from("foo"); - // Mkdir - assert!(client.mkdir(dir).is_ok()); - // cwd - assert!(client.change_dir(PathBuf::from("foo/").as_path()).is_ok()); - assert_eq!(client.wrkdir, PathBuf::from("/foo")); - // Disconnect - assert!(client.disconnect().is_ok()); - } - */ - #[test] fn test_filetransfer_sftp_uninitialized() { let file: FsFile = FsFile { @@ -1182,10 +1066,26 @@ mod tests { }; let mut sftp: SftpFileTransfer = SftpFileTransfer::new(SshKeyStorage::empty()); assert!(sftp.change_dir(Path::new("/tmp")).is_err()); + assert!(sftp + .copy( + &make_fsentry(PathBuf::from("/nowhere"), false), + PathBuf::from("/culonia").as_path() + ) + .is_err()); + assert!(sftp.exec("echo 5").is_err()); assert!(sftp.disconnect().is_err()); assert!(sftp.list_dir(Path::new("/tmp")).is_err()); assert!(sftp.mkdir(Path::new("/tmp")).is_err()); assert!(sftp.pwd().is_err()); + assert!(sftp + .remove(&make_fsentry(PathBuf::from("/nowhere"), false)) + .is_err()); + assert!(sftp + .rename( + &make_fsentry(PathBuf::from("/nowhere"), false), + PathBuf::from("/culonia").as_path() + ) + .is_err()); assert!(sftp.stat(Path::new("/tmp")).is_err()); assert!(sftp.recv_file(&file).is_err()); assert!(sftp.send_file(&file, Path::new("/tmp/omar.txt")).is_err()); diff --git a/src/fs/mod.rs b/src/fs/mod.rs index 84ca480..447d8fc 100644 --- a/src/fs/mod.rs +++ b/src/fs/mod.rs @@ -226,6 +226,28 @@ impl FsEntry { }, } } + + #[cfg(test)] + /// ### unwrap_file + /// + /// Unwrap FsEntry as FsFile + pub fn unwrap_file(self) -> FsFile { + match self { + FsEntry::File(file) => file, + _ => panic!("unwrap_file: not a file"), + } + } + + #[cfg(test)] + /// ### unwrap_dir + /// + /// Unwrap FsEntry as FsDirectory + pub fn unwrap_dir(self) -> FsDirectory { + match self { + FsEntry::Directory(dir) => dir, + _ => panic!("unwrap_dir: not a directory"), + } + } } #[cfg(test)] @@ -262,6 +284,7 @@ mod tests { assert_eq!(entry.is_dir(), true); assert_eq!(entry.is_file(), false); assert_eq!(entry.get_unix_pex(), Some((7, 5, 5))); + assert_eq!(entry.unwrap_dir().abs_path, PathBuf::from("/foo")); } #[test] @@ -294,6 +317,47 @@ mod tests { assert_eq!(entry.is_symlink(), false); assert_eq!(entry.is_dir(), false); assert_eq!(entry.is_file(), true); + assert_eq!(entry.unwrap_file().abs_path, PathBuf::from("/bar.txt")); + } + + #[test] + #[should_panic] + fn test_fs_fsentry_file_unwrap_bad() { + let t_now: SystemTime = SystemTime::now(); + let entry: FsEntry = FsEntry::File(FsFile { + name: String::from("bar.txt"), + abs_path: PathBuf::from("/bar.txt"), + last_change_time: t_now, + last_access_time: t_now, + creation_time: t_now, + size: 8192, + readonly: false, + ftype: Some(String::from("txt")), + symlink: None, // UNIX only + user: Some(0), // UNIX only + group: Some(0), // UNIX only + unix_pex: Some((6, 4, 4)), // UNIX only + }); + entry.unwrap_dir(); + } + + #[test] + #[should_panic] + fn test_fs_fsentry_dir_unwrap_bad() { + let t_now: SystemTime = SystemTime::now(); + let entry: FsEntry = FsEntry::Directory(FsDirectory { + name: String::from("foo"), + abs_path: PathBuf::from("/foo"), + last_change_time: t_now, + last_access_time: t_now, + creation_time: t_now, + readonly: false, + symlink: None, // UNIX only + user: Some(0), // UNIX only + group: Some(0), // UNIX only + unix_pex: Some((7, 5, 5)), // UNIX only + }); + entry.unwrap_file(); } #[test] diff --git a/src/host/mod.rs b/src/host/mod.rs index f3215bc..77d6bf6 100644 --- a/src/host/mod.rs +++ b/src/host/mod.rs @@ -773,10 +773,7 @@ impl Localhost { if filter.matches(dir.name.as_str()) { drained.push(FsEntry::Directory(dir.clone())); } - match self.iter_search(dir.abs_path.as_path(), filter) { - Ok(mut filtered) => drained.append(&mut filtered), - Err(err) => return Err(err), - } + drained.append(&mut self.iter_search(dir.abs_path.as_path(), filter)?); } FsEntry::File(file) => { if filter.matches(file.name.as_str()) { @@ -829,6 +826,9 @@ impl Localhost { mod tests { use super::*; + #[cfg(any(target_os = "unix", target_os = "macos", target_os = "linux"))] + use crate::utils::test_helpers::{create_sample_file, make_fsentry}; + use crate::utils::test_helpers::{make_dir_at, make_file_at}; use pretty_assertions::assert_eq; use std::fs::File; @@ -975,6 +975,7 @@ mod tests { //fs::set_permissions(file.path(), perms)?; assert!(host.open_file_write(file.path()).is_err()); } + #[cfg(any(target_os = "unix", target_os = "macos", target_os = "linux"))] #[test] fn test_host_localhost_symlinks() { @@ -1038,6 +1039,13 @@ mod tests { assert!(host .mkdir_ex(PathBuf::from("/tmp/test_dir_123456789").as_path(), true) .is_ok()); + // Fail + assert!(host + .mkdir_ex( + PathBuf::from("/aaaa/oooooo/tmp/test_dir_123456789").as_path(), + true + ) + .is_err()); } #[test] @@ -1060,6 +1068,13 @@ mod tests { let files: Vec = host.list_dir(); assert_eq!(files.len(), 1); // There should be 1 file now assert!(host.remove(files.get(0).unwrap()).is_ok()); + // Remove unexisting directory + assert!(host + .remove(&make_fsentry(PathBuf::from("/a/b/c/d"), true)) + .is_err()); + assert!(host + .remove(&make_fsentry(PathBuf::from("/aaaaaaa"), false)) + .is_err()); } #[test] @@ -1073,7 +1088,7 @@ mod tests { let mut host: Localhost = Localhost::new(PathBuf::from(tmpdir.path())).ok().unwrap(); let files: Vec = host.list_dir(); assert_eq!(files.len(), 1); // There should be 1 file now - assert_eq!(get_filename(files.get(0).unwrap()), String::from("foo.txt")); + assert_eq!(files.get(0).unwrap().get_name(), "foo.txt"); // Rename file let dst_path: PathBuf = PathBuf::from(format!("{}/bar.txt", tmpdir.path().display()).as_str()); @@ -1083,7 +1098,7 @@ mod tests { // There should be still 1 file now, but named bar.txt let files: Vec = host.list_dir(); assert_eq!(files.len(), 1); // There should be 0 files now - assert_eq!(get_filename(files.get(0).unwrap()), String::from("bar.txt")); + assert_eq!(files.get(0).unwrap().get_name(), "bar.txt"); // Fail let bad_path: PathBuf = PathBuf::from("/asdailsjoidoewojdijow/ashdiuahu"); assert!(host @@ -1131,6 +1146,13 @@ mod tests { assert!(host.copy(&file1_entry, file2_path.as_path()).is_ok()); // Verify host has two files assert_eq!(host.files.len(), 2); + // Fail copy + assert!(host + .copy( + &make_fsentry(PathBuf::from("/a/a7/a/a7a"), false), + PathBuf::from("571k422i").as_path() + ) + .is_err()); } #[cfg(any(target_os = "unix", target_os = "macos", target_os = "linux"))] @@ -1232,16 +1254,16 @@ mod tests { let tmpdir: tempfile::TempDir = tempfile::TempDir::new().unwrap(); let dir_path: &Path = tmpdir.path(); // Make files - assert!(make_sample_file(dir_path, "pippo.txt").is_ok()); - assert!(make_sample_file(dir_path, "foo.jpg").is_ok()); + assert!(make_file_at(dir_path, "pippo.txt").is_ok()); + assert!(make_file_at(dir_path, "foo.jpg").is_ok()); // Make nested struct - assert!(make_dir(dir_path, "examples").is_ok()); + assert!(make_dir_at(dir_path, "examples").is_ok()); let mut subdir: PathBuf = PathBuf::from(dir_path); subdir.push("examples/"); - assert!(make_sample_file(subdir.as_path(), "omar.txt").is_ok()); - assert!(make_sample_file(subdir.as_path(), "errors.txt").is_ok()); - assert!(make_sample_file(subdir.as_path(), "screenshot.png").is_ok()); - assert!(make_sample_file(subdir.as_path(), "examples.csv").is_ok()); + assert!(make_file_at(subdir.as_path(), "omar.txt").is_ok()); + assert!(make_file_at(subdir.as_path(), "errors.txt").is_ok()); + assert!(make_file_at(subdir.as_path(), "screenshot.png").is_ok()); + assert!(make_file_at(subdir.as_path(), "examples.csv").is_ok()); let host: Localhost = Localhost::new(PathBuf::from(dir_path)).ok().unwrap(); // Find txt files let mut result: Vec = host.find("*.txt").ok().unwrap(); @@ -1300,50 +1322,4 @@ mod tests { String::from("File already exists") ); } - - /// ### make_sample_file - /// - /// Make a file with `name` in the current directory - fn make_sample_file(dir: &Path, filename: &str) -> std::io::Result<()> { - let mut p: PathBuf = PathBuf::from(dir); - p.push(filename); - let mut file: File = File::create(p.as_path())?; - write!( - file, - "Lorem ipsum dolor sit amet, consectetur adipiscing elit.\nMauris ultricies consequat eros,\nnec scelerisque magna imperdiet metus.\n" - )?; - Ok(()) - } - - /// ### make_dir - /// - /// Make a directory in `dir` - fn make_dir(dir: &Path, dirname: &str) -> std::io::Result<()> { - let mut p: PathBuf = PathBuf::from(dir); - p.push(dirname); - std::fs::create_dir(p.as_path()) - } - - /// ### create_sample_file - /// - /// Create a sample file - #[cfg(any(target_os = "unix", target_os = "macos", target_os = "linux"))] - fn create_sample_file() -> tempfile::NamedTempFile { - // Write - let mut tmpfile: tempfile::NamedTempFile = tempfile::NamedTempFile::new().unwrap(); - write!( - tmpfile, - "Lorem ipsum dolor sit amet, consectetur adipiscing elit.\nMauris ultricies consequat eros,\nnec scelerisque magna imperdiet metus.\n" - ) - .unwrap(); - tmpfile - } - - #[cfg(any(target_os = "unix", target_os = "macos", target_os = "linux"))] - fn get_filename(entry: &FsEntry) -> String { - match entry { - FsEntry::Directory(d) => d.name.clone(), - FsEntry::File(f) => f.name.clone(), - } - } } diff --git a/src/system/bookmarks_client.rs b/src/system/bookmarks_client.rs index fdff0c9..4c7e36b 100644 --- a/src/system/bookmarks_client.rs +++ b/src/system/bookmarks_client.rs @@ -427,11 +427,12 @@ mod tests { use pretty_assertions::assert_eq; use std::thread::sleep; use std::time::Duration; + use tempfile::TempDir; #[test] fn test_system_bookmarks_new() { - let tmp_dir: tempfile::TempDir = create_tmp_dir(); + let tmp_dir: tempfile::TempDir = TempDir::new().ok().unwrap(); let (cfg_path, key_path): (PathBuf, PathBuf) = get_paths(tmp_dir.path()); // Initialize a new bookmarks client let client: BookmarksClient = @@ -454,7 +455,7 @@ mod tests { ) .is_err()); - let tmp_dir: tempfile::TempDir = create_tmp_dir(); + let tmp_dir: tempfile::TempDir = TempDir::new().ok().unwrap(); let (cfg_path, _): (PathBuf, PathBuf) = get_paths(tmp_dir.path()); assert!( BookmarksClient::new(cfg_path.as_path(), Path::new("/tmp/efnnu/omar"), 16).is_err() @@ -464,7 +465,7 @@ mod tests { #[test] fn test_system_bookmarks_new_from_existing() { - let tmp_dir: tempfile::TempDir = create_tmp_dir(); + let tmp_dir: tempfile::TempDir = TempDir::new().ok().unwrap(); let (cfg_path, key_path): (PathBuf, PathBuf) = get_paths(tmp_dir.path()); // Initialize a new bookmarks client let mut client: BookmarksClient = @@ -510,7 +511,7 @@ mod tests { #[test] fn test_system_bookmarks_manipulate_bookmarks() { - let tmp_dir: tempfile::TempDir = create_tmp_dir(); + let tmp_dir: tempfile::TempDir = TempDir::new().ok().unwrap(); let (cfg_path, key_path): (PathBuf, PathBuf) = get_paths(tmp_dir.path()); // Initialize a new bookmarks client let mut client: BookmarksClient = @@ -556,7 +557,7 @@ mod tests { #[should_panic] fn test_system_bookmarks_bad_bookmark_name() { - let tmp_dir: tempfile::TempDir = create_tmp_dir(); + let tmp_dir: tempfile::TempDir = TempDir::new().ok().unwrap(); let (cfg_path, key_path): (PathBuf, PathBuf) = get_paths(tmp_dir.path()); // Initialize a new bookmarks client let mut client: BookmarksClient = @@ -575,7 +576,7 @@ mod tests { #[test] fn test_system_bookmarks_manipulate_recents() { - let tmp_dir: tempfile::TempDir = create_tmp_dir(); + let tmp_dir: tempfile::TempDir = TempDir::new().ok().unwrap(); let (cfg_path, key_path): (PathBuf, PathBuf) = get_paths(tmp_dir.path()); // Initialize a new bookmarks client let mut client: BookmarksClient = @@ -610,7 +611,7 @@ mod tests { #[test] fn test_system_bookmarks_dup_recent() { - let tmp_dir: tempfile::TempDir = create_tmp_dir(); + let tmp_dir: tempfile::TempDir = TempDir::new().ok().unwrap(); let (cfg_path, key_path): (PathBuf, PathBuf) = get_paths(tmp_dir.path()); // Initialize a new bookmarks client let mut client: BookmarksClient = @@ -635,7 +636,7 @@ mod tests { #[test] fn test_system_bookmarks_recents_more_than_limit() { - let tmp_dir: tempfile::TempDir = create_tmp_dir(); + let tmp_dir: tempfile::TempDir = TempDir::new().ok().unwrap(); let (cfg_path, key_path): (PathBuf, PathBuf) = get_paths(tmp_dir.path()); // Initialize a new bookmarks client let mut client: BookmarksClient = @@ -681,9 +682,8 @@ mod tests { #[test] #[should_panic] - fn test_system_bookmarks_add_bookmark_empty() { - let tmp_dir: tempfile::TempDir = create_tmp_dir(); + let tmp_dir: tempfile::TempDir = TempDir::new().ok().unwrap(); let (cfg_path, key_path): (PathBuf, PathBuf) = get_paths(tmp_dir.path()); // Initialize a new bookmarks client let mut client: BookmarksClient = @@ -702,19 +702,10 @@ mod tests { /// ### get_paths /// /// Get paths for configuration and key for bookmarks - fn get_paths(dir: &Path) -> (PathBuf, PathBuf) { let k: PathBuf = PathBuf::from(dir); let mut c: PathBuf = k.clone(); c.push("bookmarks.toml"); (c, k) } - - /// ### create_tmp_dir - /// - /// Create temporary directory - - fn create_tmp_dir() -> tempfile::TempDir { - tempfile::TempDir::new().ok().unwrap() - } } diff --git a/src/system/config_client.rs b/src/system/config_client.rs index b0919b8..9caaa0b 100644 --- a/src/system/config_client.rs +++ b/src/system/config_client.rs @@ -408,10 +408,11 @@ mod tests { use pretty_assertions::assert_eq; use std::io::Read; + use tempfile::TempDir; #[test] fn test_system_config_new() { - let tmp_dir: tempfile::TempDir = create_tmp_dir(); + let tmp_dir: TempDir = TempDir::new().ok().unwrap(); let (cfg_path, ssh_keys_path): (PathBuf, PathBuf) = get_paths(tmp_dir.path()); let client: ConfigClient = ConfigClient::new(cfg_path.as_path(), ssh_keys_path.as_path()) .ok() @@ -437,14 +438,14 @@ mod tests { ConfigClient::new(Path::new("/tmp/oifoif/omar"), Path::new("/tmp/efnnu/omar"),) .is_err() ); - let tmp_dir: tempfile::TempDir = create_tmp_dir(); + let tmp_dir: TempDir = TempDir::new().ok().unwrap(); let (cfg_path, _): (PathBuf, PathBuf) = get_paths(tmp_dir.path()); assert!(ConfigClient::new(cfg_path.as_path(), Path::new("/tmp/efnnu/omar")).is_err()); } #[test] fn test_system_config_from_existing() { - let tmp_dir: tempfile::TempDir = create_tmp_dir(); + let tmp_dir: TempDir = TempDir::new().ok().unwrap(); let (cfg_path, key_path): (PathBuf, PathBuf) = get_paths(tmp_dir.path()); let mut client: ConfigClient = ConfigClient::new(cfg_path.as_path(), key_path.as_path()) .ok() @@ -477,7 +478,7 @@ mod tests { #[test] fn test_system_config_text_editor() { - let tmp_dir: tempfile::TempDir = create_tmp_dir(); + let tmp_dir: TempDir = TempDir::new().ok().unwrap(); let (cfg_path, key_path): (PathBuf, PathBuf) = get_paths(tmp_dir.path()); let mut client: ConfigClient = ConfigClient::new(cfg_path.as_path(), key_path.as_path()) .ok() @@ -488,7 +489,7 @@ mod tests { #[test] fn test_system_config_default_protocol() { - let tmp_dir: tempfile::TempDir = create_tmp_dir(); + let tmp_dir: TempDir = TempDir::new().ok().unwrap(); let (cfg_path, key_path): (PathBuf, PathBuf) = get_paths(tmp_dir.path()); let mut client: ConfigClient = ConfigClient::new(cfg_path.as_path(), key_path.as_path()) .ok() @@ -502,7 +503,7 @@ mod tests { #[test] fn test_system_config_show_hidden_files() { - let tmp_dir: tempfile::TempDir = create_tmp_dir(); + let tmp_dir: TempDir = TempDir::new().ok().unwrap(); let (cfg_path, key_path): (PathBuf, PathBuf) = get_paths(tmp_dir.path()); let mut client: ConfigClient = ConfigClient::new(cfg_path.as_path(), key_path.as_path()) .ok() @@ -513,7 +514,7 @@ mod tests { #[test] fn test_system_config_check_for_updates() { - let tmp_dir: tempfile::TempDir = create_tmp_dir(); + let tmp_dir: TempDir = TempDir::new().ok().unwrap(); let (cfg_path, key_path): (PathBuf, PathBuf) = get_paths(tmp_dir.path()); let mut client: ConfigClient = ConfigClient::new(cfg_path.as_path(), key_path.as_path()) .ok() @@ -527,7 +528,7 @@ mod tests { #[test] fn test_system_config_group_dirs() { - let tmp_dir: tempfile::TempDir = create_tmp_dir(); + let tmp_dir: TempDir = TempDir::new().ok().unwrap(); let (cfg_path, key_path): (PathBuf, PathBuf) = get_paths(tmp_dir.path()); let mut client: ConfigClient = ConfigClient::new(cfg_path.as_path(), key_path.as_path()) .ok() @@ -540,7 +541,7 @@ mod tests { #[test] fn test_system_config_local_file_fmt() { - let tmp_dir: tempfile::TempDir = create_tmp_dir(); + let tmp_dir: TempDir = TempDir::new().ok().unwrap(); let (cfg_path, key_path): (PathBuf, PathBuf) = get_paths(tmp_dir.path()); let mut client: ConfigClient = ConfigClient::new(cfg_path.as_path(), key_path.as_path()) .ok() @@ -555,7 +556,7 @@ mod tests { #[test] fn test_system_config_remote_file_fmt() { - let tmp_dir: tempfile::TempDir = create_tmp_dir(); + let tmp_dir: TempDir = TempDir::new().ok().unwrap(); let (cfg_path, key_path): (PathBuf, PathBuf) = get_paths(tmp_dir.path()); let mut client: ConfigClient = ConfigClient::new(cfg_path.as_path(), key_path.as_path()) .ok() @@ -573,7 +574,7 @@ mod tests { #[test] fn test_system_config_ssh_keys() { - let tmp_dir: tempfile::TempDir = create_tmp_dir(); + let tmp_dir: TempDir = TempDir::new().ok().unwrap(); let (cfg_path, key_path): (PathBuf, PathBuf) = get_paths(tmp_dir.path()); let mut client: ConfigClient = ConfigClient::new(cfg_path.as_path(), key_path.as_path()) .ok() @@ -637,13 +638,6 @@ mod tests { (c, k) } - /// ### create_tmp_dir - /// - /// Create temporary directory - fn create_tmp_dir() -> tempfile::TempDir { - tempfile::TempDir::new().ok().unwrap() - } - fn get_sample_rsa_key() -> String { format!( "-----BEGIN OPENSSH PRIVATE KEY-----\n{}\n-----END OPENSSH PRIVATE KEY-----", diff --git a/src/system/sshkey_storage.rs b/src/system/sshkey_storage.rs index ea6cfb4..fd5418a 100644 --- a/src/system/sshkey_storage.rs +++ b/src/system/sshkey_storage.rs @@ -87,6 +87,16 @@ impl SshKeyStorage { fn make_mapkey(host: &str, username: &str) -> String { format!("{}@{}", username, host) } + + #[cfg(test)] + /// ### add_key + /// + /// Add a key to storage + /// NOTE: available only for tests + pub fn add_key(&mut self, host: &str, username: &str, p: PathBuf) { + let key: String = Self::make_mapkey(host, username); + self.hosts.insert(key, p); + } } #[cfg(test)] @@ -100,7 +110,7 @@ mod tests { #[test] fn test_system_sshkey_storage_new() { - let tmp_dir: tempfile::TempDir = create_tmp_dir(); + let tmp_dir: tempfile::TempDir = tempfile::TempDir::new().ok().unwrap(); let (cfg_path, key_path): (PathBuf, PathBuf) = get_paths(tmp_dir.path()); let mut client: ConfigClient = ConfigClient::new(cfg_path.as_path(), key_path.as_path()) .ok() @@ -128,6 +138,16 @@ mod tests { assert_eq!(storage.hosts.len(), 0); } + #[test] + fn test_system_sshkey_storage_add() { + let mut storage: SshKeyStorage = SshKeyStorage::empty(); + storage.add_key("deskichup", "veeso", PathBuf::from("/tmp/omar")); + assert_eq!( + *storage.resolve("deskichup", "veeso").unwrap(), + PathBuf::from("/tmp/omar") + ); + } + /// ### get_paths /// /// Get paths for configuration and keys directory @@ -138,11 +158,4 @@ mod tests { c.push("config.toml"); (c, k) } - - /// ### create_tmp_dir - /// - /// Create temporary directory - fn create_tmp_dir() -> tempfile::TempDir { - tempfile::TempDir::new().ok().unwrap() - } } diff --git a/src/ui/context.rs b/src/ui/context.rs index e3d44ef..dd84e0c 100644 --- a/src/ui/context.rs +++ b/src/ui/context.rs @@ -177,7 +177,7 @@ mod tests { } #[test] - #[cfg(not(feature = "githubActions"))] + #[cfg(not(feature = "github-actions"))] fn test_ui_context() { // Prepare stuff let mut ctx: Context = Context::new(None, Some(String::from("alles kaput"))); diff --git a/src/utils/git.rs b/src/utils/git.rs index c0ef365..cab9070 100644 --- a/src/utils/git.rs +++ b/src/utils/git.rs @@ -80,7 +80,7 @@ mod tests { use super::*; #[test] - #[cfg(not(all(target_os = "macos", feature = "githubActions")))] + #[cfg(not(all(target_os = "macos", feature = "github-actions")))] fn test_utils_git_check_for_updates() { assert!(check_for_updates("100.0.0").ok().unwrap().is_none()); assert!(check_for_updates("0.0.1").ok().unwrap().is_some()); diff --git a/src/utils/mod.rs b/src/utils/mod.rs index e11bd67..f956857 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -33,3 +33,7 @@ pub mod git; pub mod parser; pub mod random; pub mod ui; + +#[cfg(test)] +#[allow(dead_code)] +pub mod test_helpers; diff --git a/src/utils/parser.rs b/src/utils/parser.rs index b5a8f41..9ac603d 100644 --- a/src/utils/parser.rs +++ b/src/utils/parser.rs @@ -186,13 +186,10 @@ pub fn parse_lstime(tm: &str, fmt_year: &str, fmt_hours: &str) -> Result dt, - Err(err) => return Err(err), - } + )? } }; // Convert datetime to system time diff --git a/src/utils/test_helpers.rs b/src/utils/test_helpers.rs new file mode 100644 index 0000000..9e6c9fe --- /dev/null +++ b/src/utils/test_helpers.rs @@ -0,0 +1,248 @@ +//! ## TestHelpers +//! +//! contains helper functions for tests + +/** + * MIT License + * + * termscp - Copyright (c) 2021 Christian Visintin + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +use crate::fs::{FsDirectory, FsEntry, FsFile}; +// ext +use std::fs::File; +#[cfg(feature = "with-containers")] +use std::fs::OpenOptions; +#[cfg(feature = "with-containers")] +use std::io::Read; +use std::io::Write; +use std::path::{Path, PathBuf}; +use std::time::SystemTime; +use tempfile::NamedTempFile; + +pub fn create_sample_file_entry() -> (FsFile, NamedTempFile) { + // Write + let tmpfile = create_sample_file(); + ( + FsFile { + name: tmpfile + .path() + .file_name() + .unwrap() + .to_string_lossy() + .to_string(), + abs_path: tmpfile.path().to_path_buf(), + last_change_time: SystemTime::UNIX_EPOCH, + last_access_time: SystemTime::UNIX_EPOCH, + creation_time: SystemTime::UNIX_EPOCH, + size: 127, + ftype: None, // File type + readonly: false, + symlink: None, // UNIX only + user: Some(0), // UNIX only + group: Some(0), // UNIX only + unix_pex: Some((6, 4, 4)), // UNIX only + }, + tmpfile, + ) +} + +pub fn create_sample_file() -> NamedTempFile { + // Write + let mut tmpfile: tempfile::NamedTempFile = tempfile::NamedTempFile::new().unwrap(); + writeln!( + tmpfile, + "Lorem ipsum dolor sit amet, consectetur adipiscing elit.Mauris ultricies consequat eros,nec scelerisque magna imperdiet metus." + ) + .unwrap(); + tmpfile +} + +/// ### make_file_at +/// +/// Make a file with `name` at specified path +pub fn make_file_at(dir: &Path, filename: &str) -> std::io::Result<()> { + let mut p: PathBuf = PathBuf::from(dir); + p.push(filename); + let mut file: File = File::create(p.as_path())?; + writeln!( + file, + "Lorem ipsum dolor sit amet, consectetur adipiscing elit.Mauris ultricies consequat eros,nec scelerisque magna imperdiet metus." + )?; + Ok(()) +} + +/// ### make_dir_at +/// +/// Make a directory in `dir` +pub fn make_dir_at(dir: &Path, dirname: &str) -> std::io::Result<()> { + let mut p: PathBuf = PathBuf::from(dir); + p.push(dirname); + std::fs::create_dir(p.as_path()) +} + +#[cfg(feature = "with-containers")] +pub fn write_file(file: &NamedTempFile, writable: &mut Box) { + let mut fhnd = OpenOptions::new() + .create(false) + .read(true) + .write(false) + .open(file.path()) + .ok() + .unwrap(); + // Read file + let mut buffer: [u8; 65536] = [0; 65536]; + assert!(fhnd.read(&mut buffer).is_ok()); + // Write file + assert!(writable.write(&buffer).is_ok()); +} + +#[cfg(feature = "with-containers")] +pub fn write_ssh_key() -> NamedTempFile { + let mut tmpfile: NamedTempFile = NamedTempFile::new().unwrap(); + writeln!( + tmpfile, + r"-----BEGIN OPENSSH PRIVATE KEY----- +b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAABFwAAAAdzc2gtcn +NhAAAAAwEAAQAAAQEAxKyYUMRCNPlb4ZV1VMofrzApu2l3wgP4Ot9wBvHsw/+RMpcHIbQK +9iQqAVp8Z+M1fJyPXTKjoJtIzuCLF6Sjo0KI7/tFTh+yPnA5QYNLZOIRZb8skumL4gwHww +5Z942FDPuUDQ30C2mZR9lr3Cd5pA8S1ZSPTAV9QQHkpgoS8cAL8QC6dp3CJjUC8wzvXh3I +oN3bTKxCpM10KMEVuWO3lM4Nvr71auB9gzo1sFJ3bwebCZIRH01FROyA/GXRiaOtJFG/9N +nWWI/iG5AJzArKpLZNHIP+FxV/NoRH0WBXm9Wq5MrBYrD1NQzm+kInpS/2sXk3m1aZWqLm +HF2NKRXSbQAAA8iI+KSniPikpwAAAAdzc2gtcnNhAAABAQDErJhQxEI0+VvhlXVUyh+vMC +m7aXfCA/g633AG8ezD/5EylwchtAr2JCoBWnxn4zV8nI9dMqOgm0jO4IsXpKOjQojv+0VO +H7I+cDlBg0tk4hFlvyyS6YviDAfDDln3jYUM+5QNDfQLaZlH2WvcJ3mkDxLVlI9MBX1BAe +SmChLxwAvxALp2ncImNQLzDO9eHcig3dtMrEKkzXQowRW5Y7eUzg2+vvVq4H2DOjWwUndv +B5sJkhEfTUVE7ID8ZdGJo60kUb/02dZYj+IbkAnMCsqktk0cg/4XFX82hEfRYFeb1arkys +FisPU1DOb6QielL/axeTebVplaouYcXY0pFdJtAAAAAwEAAQAAAP8u3PFuTVV5SfGazwIm +MgNaux82iOsAT/HWFWecQAkqqrruUw5f+YajH/riV61NE9aq2qNOkcJrgpTWtqpt980GGd +SHWlgpRWQzfIooEiDk6Pk8RVFZsEykkDlJQSIu2onZjhi5A5ojHgZoGGabDsztSqoyOjPq +6WPvGYRiDAR3leBMyp1WufBCJqAsC4L8CjPJSmnZhc5a0zXkC9Syz74Fa08tdM7bGhtvP1 +GmzuYxkgxHH2IFeoumUSBHRiTZayGuRUDel6jgEiUMxenaDKXe7FpYzMm9tQZA10Mm4LhK +5rP9nd2/KRTFRnfZMnKvtIRC9vtlSLBe14qw+4ZCl60AAACAf1kghlO3+HIWplOmk/lCL0 +w75Zz+RdvueL9UuoyNN1QrUEY420LsixgWSeRPby+Rb/hW+XSAZJQHowQ8acFJhU85So7f +4O4wcDuE4f6hpsW9tTfkCEUdLCQJ7EKLCrod6jIV7hvI6rvXiVucRpeAzdOaq4uzj2cwDd +tOdYVsnmQAAACBAOVxBsvO/Sr3rZUbNtA6KewZh/09HNGoKNaCeiD7vaSn2UJbbPRByF/o +Oo5zv8ee8r3882NnmG808XfSn7pPZAzbbTmOaJt0fmyZhivCghSNzV6njW3o0PdnC0fGZQ +ruVXgkd7RJFbsIiD4dDcF4VCjwWHfTK21EOgJUA5pN6TNvAAAAgQDbcJWRx8Uyhkj2+srb +3n2Rt6CR7kEl9cw17ItFjMn+pO81/5U2aGw0iLlX7E06TAMQC+dyW/WaxQRey8RRdtbJ1e +TNKCN34QCWkyuYRHGhcNc0quEDayPw5QWGXlP4BzjfRUcPxY9cCXLe5wDLYsX33HwOAc59 +RorU9FCmS/654wAAABFyb290QDhjNTBmZDRjMzQ1YQECAw== +-----END OPENSSH PRIVATE KEY-----" + ) + .unwrap(); + tmpfile +} + +/// ### make_fsentry +/// +/// Create a FsEntry at specified path +pub fn make_fsentry(path: PathBuf, is_dir: bool) -> FsEntry { + match is_dir { + true => FsEntry::Directory(FsDirectory { + name: path.file_name().unwrap().to_string_lossy().to_string(), + abs_path: path, + last_change_time: SystemTime::UNIX_EPOCH, + last_access_time: SystemTime::UNIX_EPOCH, + creation_time: SystemTime::UNIX_EPOCH, + readonly: false, + symlink: None, // UNIX only + user: Some(0), // UNIX only + group: Some(0), // UNIX only + unix_pex: Some((6, 4, 4)), // UNIX only + }), + false => FsEntry::File(FsFile { + name: path.file_name().unwrap().to_string_lossy().to_string(), + abs_path: path, + last_change_time: SystemTime::UNIX_EPOCH, + last_access_time: SystemTime::UNIX_EPOCH, + creation_time: SystemTime::UNIX_EPOCH, + size: 127, + ftype: None, // File type + readonly: false, + symlink: None, // UNIX only + user: Some(0), // UNIX only + group: Some(0), // UNIX only + unix_pex: Some((6, 4, 4)), // UNIX only + }), + } +} + +mod test { + use super::*; + + use pretty_assertions::assert_eq; + + #[test] + fn test_utils_test_helpers_sample_file() { + let (file, _) = create_sample_file_entry(); + assert_eq!(file.readonly, false); + } + + #[test] + #[cfg(feature = "with-containers")] + fn test_utils_test_helpers_write_file() { + let (_, temp) = create_sample_file_entry(); + let tempdest = NamedTempFile::new().unwrap(); + let mut dest: Box = Box::new( + OpenOptions::new() + .create(true) + .read(false) + .write(true) + .open(tempdest.path()) + .ok() + .unwrap(), + ); + write_file(&temp, &mut dest); + } + + #[test] + #[cfg(feature = "with-containers")] + fn test_utils_test_helpers_write_ssh_key() { + let _ = write_ssh_key(); + } + + #[test] + fn test_utils_test_helpers_make_fsentry() { + assert_eq!( + make_fsentry(PathBuf::from("/tmp/omar.txt"), false) + .unwrap_file() + .name + .as_str(), + "omar.txt" + ); + assert_eq!( + make_fsentry(PathBuf::from("/tmp/cards"), true) + .unwrap_dir() + .name + .as_str(), + "cards" + ); + } + + #[test] + fn test_utils_test_helpers_make_samples() { + let tmpdir: tempfile::TempDir = tempfile::TempDir::new().unwrap(); + assert!(make_file_at(tmpdir.path(), "omaroni.txt").is_ok()); + assert!(make_file_at(PathBuf::from("/aaaaa/bbbbb/cccc").as_path(), "readme.txt").is_err()); + assert!(make_dir_at(tmpdir.path(), "docs").is_ok()); + assert!(make_dir_at(PathBuf::from("/aaaaa/bbbbb/cccc").as_path(), "docs").is_err()); + } +} diff --git a/tests/docker-compose.yml b/tests/docker-compose.yml new file mode 100644 index 0000000..d68fed6 --- /dev/null +++ b/tests/docker-compose.yml @@ -0,0 +1,35 @@ +version: "3" +services: + openssh-server: + image: ghcr.io/linuxserver/openssh-server + environment: + - PUID=1000 + - PGID=1000 + - TZ=Europe/London + - SUDO_ACCESS=false + - PUBLIC_KEY=ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDErJhQxEI0+VvhlXVUyh+vMCm7aXfCA/g633AG8ezD/5EylwchtAr2JCoBWnxn4zV8nI9dMqOgm0jO4IsXpKOjQojv+0VOH7I+cDlBg0tk4hFlvyyS6YviDAfDDln3jYUM+5QNDfQLaZlH2WvcJ3mkDxLVlI9MBX1BAeSmChLxwAvxALp2ncImNQLzDO9eHcig3dtMrEKkzXQowRW5Y7eUzg2+vvVq4H2DOjWwUndvB5sJkhEfTUVE7ID8ZdGJo60kUb/02dZYj+IbkAnMCsqktk0cg/4XFX82hEfRYFeb1arkysFisPU1DOb6QielL/axeTebVplaouYcXY0pFdJt root@8c50fd4c345a + - PASSWORD_ACCESS=true + - USER_PASSWORD=password + - USER_NAME=sftp + ports: + - "10022:2222" + openssh-server-scp: + image: ghcr.io/linuxserver/openssh-server + environment: + - PUID=1000 + - PGID=1000 + - TZ=Europe/London + - SUDO_ACCESS=false + - PASSWORD_ACCESS=true + - PUBLIC_KEY=ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDErJhQxEI0+VvhlXVUyh+vMCm7aXfCA/g633AG8ezD/5EylwchtAr2JCoBWnxn4zV8nI9dMqOgm0jO4IsXpKOjQojv+0VOH7I+cDlBg0tk4hFlvyyS6YviDAfDDln3jYUM+5QNDfQLaZlH2WvcJ3mkDxLVlI9MBX1BAeSmChLxwAvxALp2ncImNQLzDO9eHcig3dtMrEKkzXQowRW5Y7eUzg2+vvVq4H2DOjWwUndvB5sJkhEfTUVE7ID8ZdGJo60kUb/02dZYj+IbkAnMCsqktk0cg/4XFX82hEfRYFeb1arkysFisPU1DOb6QielL/axeTebVplaouYcXY0pFdJt root@8c50fd4c345a + - USER_PASSWORD=password + - USER_NAME=sftp + ports: + - "10222:2222" + ftp-server: + image: afharo/pure-ftp + ports: + - "10021:21" + - "30000-30009:30000-30009" + environment: + - PUBLICHOST=localhost diff --git a/tests/test.sh b/tests/test.sh new file mode 100755 index 0000000..daecb00 --- /dev/null +++ b/tests/test.sh @@ -0,0 +1,29 @@ +#!/usr/bin/env sh + +if [ ! -f docker-compose.yml ]; then + set -e + cd tests/ + set +e +fi + +echo "Prepare volume..." +rm -rf /tmp/termscp-test-ftp +mkdir -p /tmp/termscp-test-ftp +echo "Building docker image..." +docker compose build +set -e +docker compose up -d +set +e + +# Go back to src root +cd .. +# Run tests +echo "Running tests" +cargo test --features with-containers -- --test-threads 1 +TEST_RESULT=$? +# Stop container +cd tests/ +echo "Stopping container..." +docker compose stop + +exit $TEST_RESULT