1
1
mirror of https://github.com/wez/wezterm.git synced 2024-12-23 13:21:38 +03:00
wezterm/wezterm-ssh/tests/e2e/sftp.rs
Wez Furlong e4ed2c93e2 encoding my preferred import grouping in the rustfmt config
This uses an unstable option in rustfmt, you `cargo +nightly fmt`
needs to be used to format the code correctly.
2022-01-17 13:50:51 -07:00

834 lines
25 KiB
Rust

use crate::sshd::session;
use assert_fs::prelude::*;
use assert_fs::TempDir;
use predicates::prelude::*;
use rstest::*;
use std::convert::TryInto;
use wezterm_ssh::{FileType, Session, SftpChannelError, SftpError, Utf8PathBuf};
// Sftp file tests
mod file;
#[inline]
fn file_type_to_str(file_type: FileType) -> &'static str {
if file_type.is_dir() {
"dir"
} else if file_type.is_file() {
"file"
} else {
"symlink"
}
}
#[rstest]
#[smol_potat::test]
#[cfg_attr(not(any(target_os = "macos", target_os = "linux")), ignore)]
async fn read_dir_should_return_list_of_directories_files_and_symlinks(#[future] session: Session) {
let session: Session = session.await;
// $TEMP/dir1/
// $TEMP/dir2/
// $TEMP/file1
// $TEMP/file2
// $TEMP/dir-link -> $TEMP/dir1/
// $TEMP/file-link -> $TEMP/file1
let temp = TempDir::new().unwrap();
let dir1 = temp.child("dir1");
dir1.create_dir_all().unwrap();
let dir2 = temp.child("dir2");
dir2.create_dir_all().unwrap();
let file1 = temp.child("file1");
file1.touch().unwrap();
let file2 = temp.child("file2");
file2.touch().unwrap();
let link_dir = temp.child("link-dir");
link_dir.symlink_to_dir(dir1.path()).unwrap();
let link_file = temp.child("link-file");
link_file.symlink_to_file(file1.path()).unwrap();
let mut contents = session
.sftp()
.read_dir(temp.path().to_path_buf())
.await
.expect("Failed to read directory")
.into_iter()
.map(|(p, s)| (p, file_type_to_str(s.ty)))
.collect::<Vec<(Utf8PathBuf, &'static str)>>();
contents.sort_unstable_by_key(|(p, _)| p.to_path_buf());
assert_eq!(
contents,
vec![
(dir1.path().to_path_buf().try_into().unwrap(), "dir"),
(dir2.path().to_path_buf().try_into().unwrap(), "dir"),
(file1.path().to_path_buf().try_into().unwrap(), "file"),
(file2.path().to_path_buf().try_into().unwrap(), "file"),
(link_dir.path().to_path_buf().try_into().unwrap(), "symlink"),
(
link_file.path().to_path_buf().try_into().unwrap(),
"symlink"
),
]
);
}
#[rstest]
#[smol_potat::test]
#[cfg_attr(not(any(target_os = "macos", target_os = "linux")), ignore)]
async fn create_dir_should_create_a_directory_on_the_remote_filesystem(#[future] session: Session) {
let session: Session = session.await;
let temp = TempDir::new().unwrap();
session
.sftp()
.create_dir(temp.child("dir").path().to_path_buf(), 0o644)
.await
.expect("Failed to create directory");
// Verify the path exists and is to a directory
temp.child("dir").assert(predicate::path::is_dir());
}
#[rstest]
#[smol_potat::test]
#[cfg_attr(not(any(target_os = "macos", target_os = "linux")), ignore)]
async fn create_dir_should_return_error_if_unable_to_create_directory(#[future] session: Session) {
let session: Session = session.await;
let temp = TempDir::new().unwrap();
// Attempt to create a nested directory structure, which is not supported
let result = session
.sftp()
.create_dir(temp.child("dir").child("dir").path().to_path_buf(), 0o644)
.await;
assert!(
result.is_err(),
"Unexpectedly succeeded in creating directory: {:?}",
result
);
// Verify the path is not a directory
temp.child("dir")
.child("dir")
.assert(predicate::path::is_dir().not());
temp.child("dir").assert(predicate::path::is_dir().not());
}
#[rstest]
#[smol_potat::test]
#[cfg_attr(not(any(target_os = "macos", target_os = "linux")), ignore)]
async fn remove_dir_should_remove_a_remote_directory(#[future] session: Session) {
let session: Session = session.await;
let temp = TempDir::new().unwrap();
// Removing an empty directory should succeed
let dir = temp.child("dir");
dir.create_dir_all().unwrap();
session
.sftp()
.remove_dir(dir.path().to_path_buf())
.await
.expect("Failed to remove directory");
// Verify the directory no longer exists
dir.assert(predicate::path::is_dir().not());
}
#[rstest]
#[smol_potat::test]
#[cfg_attr(not(any(target_os = "macos", target_os = "linux")), ignore)]
async fn remove_dir_should_return_an_error_if_failed_to_remove_directory(
#[future] session: Session,
) {
let session: Session = session.await;
let temp = TempDir::new().unwrap();
// Attempt to remove a missing path
let result = session
.sftp()
.remove_dir(temp.child("missing-dir").path().to_path_buf())
.await;
assert!(
result.is_err(),
"Unexpectedly succeeded in removing missing directory: {:?}",
result
);
// Attempt to remove a non-empty directory
let dir = temp.child("dir");
dir.create_dir_all().unwrap();
dir.child("file").touch().unwrap();
let result = session.sftp().remove_dir(dir.path().to_path_buf()).await;
assert!(
result.is_err(),
"Unexpectedly succeeded in removing non-empty directory: {:?}",
result
);
// Verify the non-empty directory still exists
dir.assert(predicate::path::is_dir());
// Attempt to remove a file (not a directory)
let file = temp.child("file");
file.touch().unwrap();
let result = session.sftp().remove_dir(file.path().to_path_buf()).await;
assert!(
result.is_err(),
"Unexpectedly succeeded in removing file: {:?}",
result
);
// Verify the file still exists
file.assert(predicate::path::is_file());
}
#[rstest]
#[smol_potat::test]
#[cfg_attr(not(any(target_os = "macos", target_os = "linux")), ignore)]
async fn metadata_should_return_metadata_about_a_file(#[future] session: Session) {
let session: Session = session.await;
let temp = TempDir::new().unwrap();
let file = temp.child("file");
file.touch().unwrap();
let metadata = session
.sftp()
.metadata(file.path().to_path_buf())
.await
.expect("Failed to get metadata for file");
// Verify that file metadata makes sense
assert!(metadata.is_file(), "Invalid file metadata returned");
}
#[rstest]
#[smol_potat::test]
#[cfg_attr(not(any(target_os = "macos", target_os = "linux")), ignore)]
async fn metadata_should_return_metadata_about_a_directory(#[future] session: Session) {
let session: Session = session.await;
let temp = TempDir::new().unwrap();
let dir = temp.child("dir");
dir.create_dir_all().unwrap();
let metadata = session
.sftp()
.metadata(dir.path().to_path_buf())
.await
.expect("Failed to get metadata for dir");
// Verify that file metadata makes sense
assert!(metadata.is_dir(), "Invalid file metadata returned");
}
#[rstest]
#[smol_potat::test]
#[cfg_attr(not(any(target_os = "macos", target_os = "linux")), ignore)]
async fn metadata_should_return_metadata_about_the_file_pointed_to_by_a_symlink(
#[future] session: Session,
) {
let session: Session = session.await;
let temp = TempDir::new().unwrap();
let file = temp.child("file");
file.touch().unwrap();
let link = temp.child("link");
link.symlink_to_file(file.path()).unwrap();
let metadata = session
.sftp()
.metadata(link.path().to_path_buf())
.await
.expect("Failed to get metadata for symlink");
// Verify that file metadata makes sense
assert!(metadata.is_file(), "Invalid file metadata returned");
assert!(metadata.ty.is_file(), "Invalid file metadata returned");
assert!(!metadata.ty.is_symlink(), "Invalid file metadata returned");
}
#[rstest]
#[smol_potat::test]
#[cfg_attr(not(any(target_os = "macos", target_os = "linux")), ignore)]
async fn metadata_should_return_metadata_about_the_dir_pointed_to_by_a_symlink(
#[future] session: Session,
) {
let session: Session = session.await;
let temp = TempDir::new().unwrap();
let dir = temp.child("dir");
dir.create_dir_all().unwrap();
let link = temp.child("link");
link.symlink_to_dir(dir.path()).unwrap();
let metadata = session
.sftp()
.metadata(link.path().to_path_buf())
.await
.expect("Failed to get metadata for symlink");
// Verify that file metadata makes sense
assert!(metadata.is_dir(), "Invalid file metadata returned");
assert!(metadata.ty.is_dir(), "Invalid file metadata returned");
assert!(!metadata.ty.is_symlink(), "Invalid file metadata returned");
}
#[rstest]
#[smol_potat::test]
#[cfg_attr(not(any(target_os = "macos", target_os = "linux")), ignore)]
async fn metadata_should_fail_if_path_missing(#[future] session: Session) {
let session: Session = session.await;
let temp = TempDir::new().unwrap();
let result = session
.sftp()
.metadata(temp.child("missing").path().to_path_buf())
.await;
assert!(
result.is_err(),
"Metadata unexpectedly succeeded: {:?}",
result
);
}
#[rstest]
#[smol_potat::test]
#[cfg_attr(not(any(target_os = "macos", target_os = "linux")), ignore)]
async fn symlink_metadata_should_return_metadata_about_a_file(#[future] session: Session) {
let session: Session = session.await;
let temp = TempDir::new().unwrap();
let file = temp.child("file");
file.touch().unwrap();
let symlink_metadata = session
.sftp()
.symlink_metadata(file.path().to_path_buf())
.await
.expect("Failed to get metadata for file");
// Verify that file metadata makes sense
assert!(symlink_metadata.is_file(), "Invalid file metadata returned");
}
#[rstest]
#[smol_potat::test]
#[cfg_attr(not(any(target_os = "macos", target_os = "linux")), ignore)]
async fn symlink_metadata_should_return_metadata_about_a_directory(#[future] session: Session) {
let session: Session = session.await;
let temp = TempDir::new().unwrap();
let dir = temp.child("dir");
dir.create_dir_all().unwrap();
let symlink_metadata = session
.sftp()
.symlink_metadata(dir.path().to_path_buf())
.await
.expect("Failed to metadata for dir");
// Verify that file metadata makes sense
assert!(symlink_metadata.is_dir(), "Invalid file metadata returned");
}
#[rstest]
#[smol_potat::test]
#[cfg_attr(not(any(target_os = "macos", target_os = "linux")), ignore)]
async fn symlink_metadata_should_return_metadata_about_symlink_pointing_to_a_file(
#[future] session: Session,
) {
let session: Session = session.await;
let temp = TempDir::new().unwrap();
let file = temp.child("file");
file.touch().unwrap();
let link = temp.child("link");
link.symlink_to_file(file.path()).unwrap();
let metadata = session
.sftp()
.symlink_metadata(link.path().to_path_buf())
.await
.expect("Failed to get metadata for symlink");
// Verify that file metadata makes sense
assert!(!metadata.is_file(), "Invalid file metadata returned");
assert!(!metadata.ty.is_file(), "Invalid file metadata returned");
assert!(metadata.ty.is_symlink(), "Invalid file metadata returned");
}
#[rstest]
#[smol_potat::test]
#[cfg_attr(not(any(target_os = "macos", target_os = "linux")), ignore)]
async fn symlink_metadata_should_return_metadata_about_symlink_pointing_to_a_directory(
#[future] session: Session,
) {
let session: Session = session.await;
let temp = TempDir::new().unwrap();
let dir = temp.child("dir");
dir.create_dir_all().unwrap();
let link = temp.child("link");
link.symlink_to_dir(dir.path()).unwrap();
let metadata = session
.sftp()
.symlink_metadata(link.path().to_path_buf())
.await
.expect("Failed to get metadata for symlink");
// Verify that file metadata makes sense
assert!(!metadata.is_dir(), "Invalid file metadata returned");
assert!(!metadata.ty.is_dir(), "Invalid file metadata returned");
assert!(metadata.ty.is_symlink(), "Invalid file metadata returned");
}
#[rstest]
#[smol_potat::test]
#[cfg_attr(not(any(target_os = "macos", target_os = "linux")), ignore)]
async fn symlink_metadata_should_fail_if_path_missing(#[future] session: Session) {
let session: Session = session.await;
let temp = TempDir::new().unwrap();
let result = session
.sftp()
.symlink_metadata(temp.child("missing").path().to_path_buf())
.await;
assert!(
result.is_err(),
"symlink_metadata unexpectedly succeeded: {:?}",
result
);
}
#[rstest]
#[smol_potat::test]
#[cfg_attr(not(any(target_os = "macos", target_os = "linux")), ignore)]
async fn symlink_should_create_symlink_pointing_to_file(#[future] session: Session) {
let session: Session = session.await;
let temp = TempDir::new().unwrap();
let file = temp.child("file");
file.touch().unwrap();
let link = temp.child("link");
session
.sftp()
.symlink(file.path().to_path_buf(), link.path().to_path_buf())
.await
.expect("Failed to create symlink");
assert!(
std::fs::symlink_metadata(link.path())
.unwrap()
.file_type()
.is_symlink(),
"Symlink is not a symlink!"
);
// TODO: This fails even though the type is a symlink:
// https://github.com/assert-rs/assert_fs/issues/70
// link.assert(predicate::path::is_symlink());
}
#[rstest]
#[smol_potat::test]
#[cfg_attr(not(any(target_os = "macos", target_os = "linux")), ignore)]
async fn symlink_should_create_symlink_pointing_to_directory(#[future] session: Session) {
let session: Session = session.await;
let temp = TempDir::new().unwrap();
let dir = temp.child("dir");
dir.create_dir_all().unwrap();
let link = temp.child("link");
session
.sftp()
.symlink(dir.path().to_path_buf(), link.path().to_path_buf())
.await
.expect("Failed to create symlink");
link.assert(predicate::path::is_symlink());
}
#[rstest]
#[smol_potat::test]
#[cfg_attr(not(any(target_os = "macos", target_os = "linux")), ignore)]
async fn symlink_should_succeed_even_if_path_missing(#[future] session: Session) {
let session: Session = session.await;
let temp = TempDir::new().unwrap();
let file = temp.child("file");
let link = temp.child("link");
session
.sftp()
.symlink(file.path().to_path_buf(), link.path().to_path_buf())
.await
.expect("Failed to create symlink");
link.assert(predicate::path::is_symlink());
}
#[rstest]
#[smol_potat::test]
#[cfg_attr(not(any(target_os = "macos", target_os = "linux")), ignore)]
async fn read_link_should_return_the_target_of_the_symlink(#[future] session: Session) {
let session: Session = session.await;
let temp = TempDir::new().unwrap();
// Test a symlink to a directory
let dir = temp.child("dir");
dir.create_dir_all().unwrap();
let link = temp.child("link");
link.symlink_to_dir(dir.path()).unwrap();
let path = session
.sftp()
.read_link(link.path().to_path_buf())
.await
.expect("Failed to read symlink");
assert_eq!(path, dir.path());
// Test a symlink to a file
let file = temp.child("file");
file.touch().unwrap();
let link = temp.child("link2");
link.symlink_to_file(file.path()).unwrap();
let path = session
.sftp()
.read_link(link.path().to_path_buf())
.await
.expect("Failed to read symlink");
assert_eq!(path, file.path());
}
#[rstest]
#[smol_potat::test]
#[cfg_attr(not(any(target_os = "macos", target_os = "linux")), ignore)]
async fn read_link_should_fail_if_path_is_not_a_symlink(#[future] session: Session) {
let session: Session = session.await;
let temp = TempDir::new().unwrap();
// Test missing path
let result = session
.sftp()
.read_link(temp.child("missing").path().to_path_buf())
.await;
assert!(
result.is_err(),
"Unexpectedly read link for missing path: {:?}",
result
);
// Test a directory
let dir = temp.child("dir");
dir.create_dir_all().unwrap();
let result = session.sftp().read_link(dir.path().to_path_buf()).await;
assert!(
result.is_err(),
"Unexpectedly read link for directory: {:?}",
result
);
// Test a file
let file = temp.child("file");
file.touch().unwrap();
let result = session.sftp().read_link(file.path().to_path_buf()).await;
assert!(
result.is_err(),
"Unexpectedly read link for file: {:?}",
result
);
}
#[rstest]
#[smol_potat::test]
#[cfg_attr(not(any(target_os = "macos", target_os = "linux")), ignore)]
async fn canonicalize_should_resolve_absolute_path_for_relative_path(#[future] session: Session) {
let session: Session = session.await;
// For resolving parts of a path, all components must exist
let temp = TempDir::new().unwrap();
temp.child("hello").create_dir_all().unwrap();
temp.child("world").touch().unwrap();
let rel = temp.child(".").child("hello").child("..").child("world");
// NOTE: Because sftp realpath can still resolve symlinks within a missing path, there
// is no guarantee that the resulting path matches the missing path. In fact,
// on mac the /tmp dir is a symlink to /private/tmp; so, we cannot successfully
// check the accuracy of the path itself, meaning that we can only validate
// that the operation was okay.
let result = session.sftp().canonicalize(rel.path().to_path_buf()).await;
assert!(
result.is_ok(),
"Canonicalize unexpectedly failed: {:?}",
result
);
}
#[rstest]
#[smol_potat::test]
#[cfg_attr(not(any(target_os = "macos", target_os = "linux")), ignore)]
async fn canonicalize_should_either_return_resolved_path_or_error_if_missing(
#[future] session: Session,
) {
let session: Session = session.await;
let temp = TempDir::new().unwrap();
let missing = temp.child("missing");
// NOTE: Because sftp realpath can still resolve symlinks within a missing path, there
// is no guarantee that the resulting path matches the missing path. In fact,
// on mac the /tmp dir is a symlink to /private/tmp; so, we cannot successfully
// check the accuracy of the path itself, meaning that we can only validate
// that the operation was okay.
//
// Additionally, this has divergent behavior. On some platforms, this returns
// the path as is whereas on others this returns a missing path error. We
// have to support both checks.
let result = session
.sftp()
.canonicalize(missing.path().to_path_buf())
.await;
match result {
Ok(_) => {}
Err(SftpChannelError::Sftp(SftpError::NoSuchFile)) => {}
#[cfg(feature = "libssh-rs")]
Err(SftpChannelError::LibSsh(libssh_rs::Error::Sftp(_))) => {}
x => panic!(
"Unexpected result from canonicalize({}: {:?}",
missing.path().display(),
x
),
}
}
#[rstest]
#[smol_potat::test]
#[cfg_attr(not(any(target_os = "macos", target_os = "linux")), ignore)]
async fn canonicalize_should_fail_if_resolving_missing_path_with_dots(#[future] session: Session) {
let session: Session = session.await;
let temp = TempDir::new().unwrap();
let missing = temp.child(".").child("hello").child("..").child("world");
let result = session
.sftp()
.canonicalize(missing.path().to_path_buf())
.await;
assert!(result.is_err(), "Canonicalize unexpectedly succeeded");
}
#[rstest]
#[smol_potat::test]
#[cfg_attr(not(any(target_os = "macos", target_os = "linux")), ignore)]
async fn rename_should_support_singular_file(#[future] session: Session) {
let session: Session = session.await;
let temp = TempDir::new().unwrap();
let file = temp.child("file");
file.write_str("some text").unwrap();
let dst = temp.child("dst");
session
.sftp()
.rename(
file.path().to_path_buf(),
dst.path().to_path_buf(),
Default::default(),
)
.await
.expect("Failed to rename file");
// Verify that file was moved to destination
file.assert(predicate::path::missing());
dst.assert("some text");
}
#[rstest]
#[smol_potat::test]
#[cfg_attr(not(any(target_os = "macos", target_os = "linux")), ignore)]
async fn rename_should_support_dirtectory(#[future] session: Session) {
let session: Session = session.await;
let temp = TempDir::new().unwrap();
let dir = temp.child("dir");
dir.create_dir_all().unwrap();
let dir_file = dir.child("file");
dir_file.write_str("some text").unwrap();
let dir_dir = dir.child("dir");
dir_dir.create_dir_all().unwrap();
let dst = temp.child("dst");
session
.sftp()
.rename(
dir.path().to_path_buf(),
dst.path().to_path_buf(),
Default::default(),
)
.await
.expect("Failed to rename directory");
// Verify that directory was moved to destination
dir.assert(predicate::path::missing());
dir_file.assert(predicate::path::missing());
dir_dir.assert(predicate::path::missing());
dst.assert(predicate::path::is_dir());
dst.child("file").assert("some text");
dst.child("dir").assert(predicate::path::is_dir());
}
#[rstest]
#[smol_potat::test]
#[cfg_attr(not(any(target_os = "macos", target_os = "linux")), ignore)]
async fn rename_should_fail_if_source_path_missing(#[future] session: Session) {
let session: Session = session.await;
let temp = TempDir::new().unwrap();
let missing = temp.child("missing");
let dst = temp.child("dst");
let result = session
.sftp()
.rename(
missing.path().to_path_buf(),
dst.path().to_path_buf(),
Default::default(),
)
.await;
assert!(
result.is_err(),
"Rename unexpectedly succeeded with missing path: {:?}",
result
);
}
#[rstest]
#[smol_potat::test]
#[cfg_attr(not(any(target_os = "macos", target_os = "linux")), ignore)]
async fn remove_file_should_remove_file(#[future] session: Session) {
let session: Session = session.await;
let temp = TempDir::new().unwrap();
let file = temp.child("file");
file.touch().unwrap();
session
.sftp()
.remove_file(file.path().to_path_buf())
.await
.expect("Failed to remove file");
file.assert(predicate::path::missing());
}
#[rstest]
#[smol_potat::test]
#[cfg_attr(not(any(target_os = "macos", target_os = "linux")), ignore)]
async fn remove_file_should_remove_symlink_to_file(#[future] session: Session) {
let session: Session = session.await;
let temp = TempDir::new().unwrap();
let file = temp.child("file");
file.touch().unwrap();
let link = temp.child("link");
link.symlink_to_file(file.path()).unwrap();
session
.sftp()
.remove_file(link.path().to_path_buf())
.await
.expect("Failed to remove symlink");
// Verify link removed but file still exists
link.assert(predicate::path::missing());
file.assert(predicate::path::is_file());
}
#[rstest]
#[smol_potat::test]
#[cfg_attr(not(any(target_os = "macos", target_os = "linux")), ignore)]
async fn remove_file_should_remove_symlink_to_directory(#[future] session: Session) {
let session: Session = session.await;
let temp = TempDir::new().unwrap();
let dir = temp.child("dir");
dir.create_dir_all().unwrap();
let link = temp.child("link");
link.symlink_to_dir(dir.path()).unwrap();
session
.sftp()
.remove_file(link.path().to_path_buf())
.await
.expect("Failed to remove symlink");
// Verify link removed but directory still exists
link.assert(predicate::path::missing());
dir.assert(predicate::path::is_dir());
}
#[rstest]
#[smol_potat::test]
#[cfg_attr(not(any(target_os = "macos", target_os = "linux")), ignore)]
async fn remove_file_should_fail_if_path_to_directory(#[future] session: Session) {
let session: Session = session.await;
let temp = TempDir::new().unwrap();
let dir = temp.child("dir");
dir.create_dir_all().unwrap();
let result = session.sftp().remove_file(dir.path().to_path_buf()).await;
assert!(
result.is_err(),
"Unexpectedly removed directory: {:?}",
result
);
// Verify directory still here
dir.assert(predicate::path::is_dir());
}
#[rstest]
#[smol_potat::test]
#[cfg_attr(not(any(target_os = "macos", target_os = "linux")), ignore)]
async fn remove_file_should_fail_if_path_missing(#[future] session: Session) {
let session: Session = session.await;
let temp = TempDir::new().unwrap();
let result = session
.sftp()
.remove_file(temp.child("missing").path().to_path_buf())
.await;
assert!(
result.is_err(),
"Unexpectedly removed missing path: {:?}",
result
);
}