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};
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;
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");
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();
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
);
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();
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");
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();
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
);
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
);
dir.assert(predicate::path::is_dir());
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
);
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");
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");
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");
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");
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");
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");
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");
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");
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!"
);
}
#[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();
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());
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();
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
);
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
);
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;
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");
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");
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");
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");
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");
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");
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
);
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
);
}