mirror of https://github.com/chipsenkbeil/distant
You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1872 lines
57 KiB
Rust
1872 lines
57 KiB
Rust
use crate::sshd::*;
|
|
use assert_fs::{prelude::*, TempDir};
|
|
use distant_core::{
|
|
FileType, Request, RequestData, Response, ResponseData, RunningProcess, Session,
|
|
};
|
|
use once_cell::sync::Lazy;
|
|
use predicates::prelude::*;
|
|
use rstest::*;
|
|
use std::{
|
|
env,
|
|
path::{Path, PathBuf},
|
|
time::Duration,
|
|
};
|
|
|
|
static TEMP_SCRIPT_DIR: Lazy<TempDir> = Lazy::new(|| TempDir::new().unwrap());
|
|
static SCRIPT_RUNNER: Lazy<String> = Lazy::new(|| String::from("bash"));
|
|
|
|
static ECHO_ARGS_TO_STDOUT_SH: Lazy<assert_fs::fixture::ChildPath> = Lazy::new(|| {
|
|
let script = TEMP_SCRIPT_DIR.child("echo_args_to_stdout.sh");
|
|
script
|
|
.write_str(indoc::indoc!(
|
|
r#"
|
|
#/usr/bin/env bash
|
|
printf "%s" "$*"
|
|
"#
|
|
))
|
|
.unwrap();
|
|
script
|
|
});
|
|
|
|
static ECHO_ARGS_TO_STDERR_SH: Lazy<assert_fs::fixture::ChildPath> = Lazy::new(|| {
|
|
let script = TEMP_SCRIPT_DIR.child("echo_args_to_stderr.sh");
|
|
script
|
|
.write_str(indoc::indoc!(
|
|
r#"
|
|
#/usr/bin/env bash
|
|
printf "%s" "$*" 1>&2
|
|
"#
|
|
))
|
|
.unwrap();
|
|
script
|
|
});
|
|
|
|
static ECHO_STDIN_TO_STDOUT_SH: Lazy<assert_fs::fixture::ChildPath> = Lazy::new(|| {
|
|
let script = TEMP_SCRIPT_DIR.child("echo_stdin_to_stdout.sh");
|
|
script
|
|
.write_str(indoc::indoc!(
|
|
r#"
|
|
#/usr/bin/env bash
|
|
while IFS= read; do echo "$REPLY"; done
|
|
"#
|
|
))
|
|
.unwrap();
|
|
script
|
|
});
|
|
|
|
static SLEEP_SH: Lazy<assert_fs::fixture::ChildPath> = Lazy::new(|| {
|
|
let script = TEMP_SCRIPT_DIR.child("sleep.sh");
|
|
script
|
|
.write_str(indoc::indoc!(
|
|
r#"
|
|
#!/usr/bin/env bash
|
|
sleep "$1"
|
|
"#
|
|
))
|
|
.unwrap();
|
|
script
|
|
});
|
|
|
|
static DOES_NOT_EXIST_BIN: Lazy<assert_fs::fixture::ChildPath> =
|
|
Lazy::new(|| TEMP_SCRIPT_DIR.child("does_not_exist_bin"));
|
|
|
|
#[rstest]
|
|
#[tokio::test]
|
|
async fn file_read_should_send_error_if_fails_to_read_file(#[future] session: Session) {
|
|
let mut session = session.await;
|
|
let temp = TempDir::new().unwrap();
|
|
let path = temp.child("missing-file").path().to_path_buf();
|
|
let req = Request::new("test-tenant", vec![RequestData::FileRead { path }]);
|
|
let res = session.send(req).await.unwrap();
|
|
|
|
assert_eq!(res.payload.len(), 1, "Wrong payload size");
|
|
assert!(
|
|
matches!(res.payload[0], ResponseData::Error(_)),
|
|
"Unexpected response: {:?}",
|
|
res.payload[0]
|
|
);
|
|
}
|
|
|
|
#[rstest]
|
|
#[tokio::test]
|
|
async fn file_read_should_send_blob_with_file_contents(#[future] session: Session) {
|
|
let mut session = session.await;
|
|
let temp = TempDir::new().unwrap();
|
|
let file = temp.child("test-file");
|
|
file.write_str("some file contents").unwrap();
|
|
|
|
let req = Request::new(
|
|
"test-tenant",
|
|
vec![RequestData::FileRead {
|
|
path: file.path().to_path_buf(),
|
|
}],
|
|
);
|
|
let res = session.send(req).await.unwrap();
|
|
|
|
assert_eq!(res.payload.len(), 1, "Wrong payload size");
|
|
match &res.payload[0] {
|
|
ResponseData::Blob { data } => assert_eq!(data, b"some file contents"),
|
|
x => panic!("Unexpected response: {:?}", x),
|
|
}
|
|
}
|
|
|
|
#[rstest]
|
|
#[tokio::test]
|
|
async fn file_read_text_should_send_error_if_fails_to_read_file(#[future] session: Session) {
|
|
let mut session = session.await;
|
|
let temp = TempDir::new().unwrap();
|
|
let path = temp.child("missing-file").path().to_path_buf();
|
|
let req = Request::new("test-tenant", vec![RequestData::FileReadText { path }]);
|
|
let res = session.send(req).await.unwrap();
|
|
|
|
assert_eq!(res.payload.len(), 1, "Wrong payload size");
|
|
assert!(
|
|
matches!(res.payload[0], ResponseData::Error(_)),
|
|
"Unexpected response: {:?}",
|
|
res.payload[0]
|
|
);
|
|
}
|
|
|
|
#[rstest]
|
|
#[tokio::test]
|
|
async fn file_read_text_should_send_text_with_file_contents(#[future] session: Session) {
|
|
let mut session = session.await;
|
|
let temp = TempDir::new().unwrap();
|
|
let file = temp.child("test-file");
|
|
file.write_str("some file contents").unwrap();
|
|
|
|
let req = Request::new(
|
|
"test-tenant",
|
|
vec![RequestData::FileReadText {
|
|
path: file.path().to_path_buf(),
|
|
}],
|
|
);
|
|
let res = session.send(req).await.unwrap();
|
|
|
|
assert_eq!(res.payload.len(), 1, "Wrong payload size");
|
|
match &res.payload[0] {
|
|
ResponseData::Text { data } => assert_eq!(data, "some file contents"),
|
|
x => panic!("Unexpected response: {:?}", x),
|
|
}
|
|
}
|
|
|
|
#[rstest]
|
|
#[tokio::test]
|
|
async fn file_write_should_send_error_if_fails_to_write_file(#[future] session: Session) {
|
|
let mut session = session.await;
|
|
// Create a temporary path and add to it to ensure that there are
|
|
// extra components that don't exist to cause writing to fail
|
|
let temp = TempDir::new().unwrap();
|
|
let file = temp.child("dir").child("test-file");
|
|
|
|
let req = Request::new(
|
|
"test-tenant",
|
|
vec![RequestData::FileWrite {
|
|
path: file.path().to_path_buf(),
|
|
data: b"some text".to_vec(),
|
|
}],
|
|
);
|
|
|
|
let res = session.send(req).await.unwrap();
|
|
assert_eq!(res.payload.len(), 1, "Wrong payload size");
|
|
assert!(
|
|
matches!(res.payload[0], ResponseData::Error(_)),
|
|
"Unexpected response: {:?}",
|
|
res.payload[0]
|
|
);
|
|
|
|
// Also verify that we didn't actually create the file
|
|
file.assert(predicate::path::missing());
|
|
}
|
|
|
|
#[rstest]
|
|
#[tokio::test]
|
|
async fn file_write_should_send_ok_when_successful(#[future] session: Session) {
|
|
let mut session = session.await;
|
|
// Path should point to a file that does not exist, but all
|
|
// other components leading up to it do
|
|
let temp = TempDir::new().unwrap();
|
|
let file = temp.child("test-file");
|
|
|
|
let req = Request::new(
|
|
"test-tenant",
|
|
vec![RequestData::FileWrite {
|
|
path: file.path().to_path_buf(),
|
|
data: b"some text".to_vec(),
|
|
}],
|
|
);
|
|
|
|
let res = session.send(req).await.unwrap();
|
|
assert_eq!(res.payload.len(), 1, "Wrong payload size");
|
|
assert!(
|
|
matches!(res.payload[0], ResponseData::Ok),
|
|
"Unexpected response: {:?}",
|
|
res.payload[0]
|
|
);
|
|
|
|
// Also verify that we actually did create the file
|
|
// with the associated contents
|
|
file.assert("some text");
|
|
}
|
|
|
|
#[rstest]
|
|
#[tokio::test]
|
|
async fn file_write_text_should_send_error_if_fails_to_write_file(#[future] session: Session) {
|
|
let mut session = session.await;
|
|
// Create a temporary path and add to it to ensure that there are
|
|
// extra components that don't exist to cause writing to fail
|
|
let temp = TempDir::new().unwrap();
|
|
let file = temp.child("dir").child("test-file");
|
|
|
|
let req = Request::new(
|
|
"test-tenant",
|
|
vec![RequestData::FileWriteText {
|
|
path: file.path().to_path_buf(),
|
|
text: String::from("some text"),
|
|
}],
|
|
);
|
|
|
|
let res = session.send(req).await.unwrap();
|
|
assert_eq!(res.payload.len(), 1, "Wrong payload size");
|
|
assert!(
|
|
matches!(res.payload[0], ResponseData::Error(_)),
|
|
"Unexpected response: {:?}",
|
|
res.payload[0]
|
|
);
|
|
|
|
// Also verify that we didn't actually create the file
|
|
file.assert(predicate::path::missing());
|
|
}
|
|
|
|
#[rstest]
|
|
#[tokio::test]
|
|
async fn file_write_text_should_send_ok_when_successful(#[future] session: Session) {
|
|
let mut session = session.await;
|
|
// Path should point to a file that does not exist, but all
|
|
// other components leading up to it do
|
|
let temp = TempDir::new().unwrap();
|
|
let file = temp.child("test-file");
|
|
|
|
let req = Request::new(
|
|
"test-tenant",
|
|
vec![RequestData::FileWriteText {
|
|
path: file.path().to_path_buf(),
|
|
text: String::from("some text"),
|
|
}],
|
|
);
|
|
|
|
let res = session.send(req).await.unwrap();
|
|
assert_eq!(res.payload.len(), 1, "Wrong payload size");
|
|
assert!(
|
|
matches!(res.payload[0], ResponseData::Ok),
|
|
"Unexpected response: {:?}",
|
|
res.payload[0]
|
|
);
|
|
|
|
// Also verify that we actually did create the file
|
|
// with the associated contents
|
|
file.assert("some text");
|
|
}
|
|
|
|
#[rstest]
|
|
#[tokio::test]
|
|
async fn file_append_should_send_error_if_fails_to_create_file(#[future] session: Session) {
|
|
let mut session = session.await;
|
|
// Create a temporary path and add to it to ensure that there are
|
|
// extra components that don't exist to cause writing to fail
|
|
let temp = TempDir::new().unwrap();
|
|
let file = temp.child("dir").child("test-file");
|
|
|
|
let req = Request::new(
|
|
"test-tenant",
|
|
vec![RequestData::FileAppend {
|
|
path: file.path().to_path_buf(),
|
|
data: b"some extra contents".to_vec(),
|
|
}],
|
|
);
|
|
|
|
let res = session.send(req).await.unwrap();
|
|
assert_eq!(res.payload.len(), 1, "Wrong payload size");
|
|
assert!(
|
|
matches!(res.payload[0], ResponseData::Error(_)),
|
|
"Unexpected response: {:?}",
|
|
res.payload[0]
|
|
);
|
|
|
|
// Also verify that we didn't actually create the file
|
|
file.assert(predicate::path::missing());
|
|
}
|
|
|
|
#[rstest]
|
|
#[tokio::test]
|
|
async fn file_append_should_send_ok_when_successful(#[future] session: Session) {
|
|
let mut session = session.await;
|
|
// Create a temporary file and fill it with some contents
|
|
let temp = TempDir::new().unwrap();
|
|
let file = temp.child("test-file");
|
|
file.write_str("some file contents").unwrap();
|
|
|
|
let req = Request::new(
|
|
"test-tenant",
|
|
vec![RequestData::FileAppend {
|
|
path: file.path().to_path_buf(),
|
|
data: b"some extra contents".to_vec(),
|
|
}],
|
|
);
|
|
|
|
let res = session.send(req).await.unwrap();
|
|
assert_eq!(res.payload.len(), 1, "Wrong payload size");
|
|
assert!(
|
|
matches!(res.payload[0], ResponseData::Ok),
|
|
"Unexpected response: {:?}",
|
|
res.payload[0]
|
|
);
|
|
|
|
// Yield to allow chance to finish appending to file
|
|
tokio::time::sleep(Duration::from_millis(50)).await;
|
|
|
|
// Also verify that we actually did append to the file
|
|
file.assert("some file contentssome extra contents");
|
|
}
|
|
|
|
#[rstest]
|
|
#[tokio::test]
|
|
async fn file_append_text_should_send_error_if_fails_to_create_file(#[future] session: Session) {
|
|
let mut session = session.await;
|
|
// Create a temporary path and add to it to ensure that there are
|
|
// extra components that don't exist to cause writing to fail
|
|
let temp = TempDir::new().unwrap();
|
|
let file = temp.child("dir").child("test-file");
|
|
|
|
let req = Request::new(
|
|
"test-tenant",
|
|
vec![RequestData::FileAppendText {
|
|
path: file.path().to_path_buf(),
|
|
text: String::from("some extra contents"),
|
|
}],
|
|
);
|
|
|
|
let res = session.send(req).await.unwrap();
|
|
assert_eq!(res.payload.len(), 1, "Wrong payload size");
|
|
assert!(
|
|
matches!(res.payload[0], ResponseData::Error(_)),
|
|
"Unexpected response: {:?}",
|
|
res.payload[0]
|
|
);
|
|
|
|
// Also verify that we didn't actually create the file
|
|
file.assert(predicate::path::missing());
|
|
}
|
|
|
|
#[rstest]
|
|
#[tokio::test]
|
|
async fn file_append_text_should_send_ok_when_successful(#[future] session: Session) {
|
|
let mut session = session.await;
|
|
// Create a temporary file and fill it with some contents
|
|
let temp = TempDir::new().unwrap();
|
|
let file = temp.child("test-file");
|
|
file.write_str("some file contents").unwrap();
|
|
|
|
let req = Request::new(
|
|
"test-tenant",
|
|
vec![RequestData::FileAppendText {
|
|
path: file.path().to_path_buf(),
|
|
text: String::from("some extra contents"),
|
|
}],
|
|
);
|
|
|
|
let res = session.send(req).await.unwrap();
|
|
assert_eq!(res.payload.len(), 1, "Wrong payload size");
|
|
assert!(
|
|
matches!(res.payload[0], ResponseData::Ok),
|
|
"Unexpected response: {:?}",
|
|
res.payload[0]
|
|
);
|
|
|
|
// Yield to allow chance to finish appending to file
|
|
tokio::time::sleep(Duration::from_millis(50)).await;
|
|
|
|
// Also verify that we actually did append to the file
|
|
file.assert("some file contentssome extra contents");
|
|
}
|
|
|
|
#[rstest]
|
|
#[tokio::test]
|
|
async fn dir_read_should_send_error_if_directory_does_not_exist(#[future] session: Session) {
|
|
let mut session = session.await;
|
|
let temp = TempDir::new().unwrap();
|
|
let dir = temp.child("test-dir");
|
|
|
|
let req = Request::new(
|
|
"test-tenant",
|
|
vec![RequestData::DirRead {
|
|
path: dir.path().to_path_buf(),
|
|
depth: 0,
|
|
absolute: false,
|
|
canonicalize: false,
|
|
include_root: false,
|
|
}],
|
|
);
|
|
|
|
let res = session.send(req).await.unwrap();
|
|
assert_eq!(res.payload.len(), 1, "Wrong payload size");
|
|
assert!(
|
|
matches!(res.payload[0], ResponseData::Error(_)),
|
|
"Unexpected response: {:?}",
|
|
res.payload[0]
|
|
);
|
|
}
|
|
|
|
// /root/
|
|
// /root/file1
|
|
// /root/link1 -> /root/sub1/file2
|
|
// /root/sub1/
|
|
// /root/sub1/file2
|
|
async fn setup_dir() -> TempDir {
|
|
let root_dir = TempDir::new().unwrap();
|
|
root_dir.child("file1").touch().unwrap();
|
|
|
|
let sub1 = root_dir.child("sub1");
|
|
sub1.create_dir_all().unwrap();
|
|
|
|
let file2 = sub1.child("file2");
|
|
file2.touch().unwrap();
|
|
|
|
let link1 = root_dir.child("link1");
|
|
link1.symlink_to_file(file2.path()).unwrap();
|
|
|
|
root_dir
|
|
}
|
|
|
|
#[rstest]
|
|
#[tokio::test]
|
|
async fn dir_read_should_support_depth_limits(#[future] session: Session) {
|
|
let mut session = session.await;
|
|
// Create directory with some nested items
|
|
let root_dir = setup_dir().await;
|
|
|
|
let req = Request::new(
|
|
"test-tenant",
|
|
vec![RequestData::DirRead {
|
|
path: root_dir.path().to_path_buf(),
|
|
depth: 1,
|
|
absolute: false,
|
|
canonicalize: false,
|
|
include_root: false,
|
|
}],
|
|
);
|
|
|
|
let res = session.send(req).await.unwrap();
|
|
assert_eq!(res.payload.len(), 1, "Wrong payload size");
|
|
match &res.payload[0] {
|
|
ResponseData::DirEntries { entries, .. } => {
|
|
assert_eq!(entries.len(), 3, "Wrong number of entries found");
|
|
|
|
assert_eq!(entries[0].file_type, FileType::File);
|
|
assert_eq!(entries[0].path, Path::new("file1"));
|
|
assert_eq!(entries[0].depth, 1);
|
|
|
|
assert_eq!(entries[1].file_type, FileType::Symlink);
|
|
assert_eq!(entries[1].path, Path::new("link1"));
|
|
assert_eq!(entries[1].depth, 1);
|
|
|
|
assert_eq!(entries[2].file_type, FileType::Dir);
|
|
assert_eq!(entries[2].path, Path::new("sub1"));
|
|
assert_eq!(entries[2].depth, 1);
|
|
}
|
|
x => panic!("Unexpected response: {:?}", x),
|
|
}
|
|
}
|
|
|
|
#[rstest]
|
|
#[tokio::test]
|
|
async fn dir_read_should_support_unlimited_depth_using_zero(#[future] session: Session) {
|
|
let mut session = session.await;
|
|
// Create directory with some nested items
|
|
let root_dir = setup_dir().await;
|
|
|
|
let req = Request::new(
|
|
"test-tenant",
|
|
vec![RequestData::DirRead {
|
|
path: root_dir.path().to_path_buf(),
|
|
depth: 0,
|
|
absolute: false,
|
|
canonicalize: false,
|
|
include_root: false,
|
|
}],
|
|
);
|
|
|
|
let res = session.send(req).await.unwrap();
|
|
assert_eq!(res.payload.len(), 1, "Wrong payload size");
|
|
match &res.payload[0] {
|
|
ResponseData::DirEntries { entries, .. } => {
|
|
assert_eq!(entries.len(), 4, "Wrong number of entries found");
|
|
|
|
assert_eq!(entries[0].file_type, FileType::File);
|
|
assert_eq!(entries[0].path, Path::new("file1"));
|
|
assert_eq!(entries[0].depth, 1);
|
|
|
|
assert_eq!(entries[1].file_type, FileType::Symlink);
|
|
assert_eq!(entries[1].path, Path::new("link1"));
|
|
assert_eq!(entries[1].depth, 1);
|
|
|
|
assert_eq!(entries[2].file_type, FileType::Dir);
|
|
assert_eq!(entries[2].path, Path::new("sub1"));
|
|
assert_eq!(entries[2].depth, 1);
|
|
|
|
assert_eq!(entries[3].file_type, FileType::File);
|
|
assert_eq!(entries[3].path, Path::new("sub1").join("file2"));
|
|
assert_eq!(entries[3].depth, 2);
|
|
}
|
|
x => panic!("Unexpected response: {:?}", x),
|
|
}
|
|
}
|
|
|
|
#[rstest]
|
|
#[tokio::test]
|
|
async fn dir_read_should_support_including_directory_in_returned_entries(
|
|
#[future] session: Session,
|
|
) {
|
|
let mut session = session.await;
|
|
// Create directory with some nested items
|
|
let root_dir = setup_dir().await;
|
|
|
|
let req = Request::new(
|
|
"test-tenant",
|
|
vec![RequestData::DirRead {
|
|
path: root_dir.path().to_path_buf(),
|
|
depth: 1,
|
|
absolute: false,
|
|
canonicalize: false,
|
|
include_root: true,
|
|
}],
|
|
);
|
|
|
|
let res = session.send(req).await.unwrap();
|
|
assert_eq!(res.payload.len(), 1, "Wrong payload size");
|
|
match &res.payload[0] {
|
|
ResponseData::DirEntries { entries, .. } => {
|
|
assert_eq!(entries.len(), 4, "Wrong number of entries found");
|
|
|
|
// NOTE: Root entry is always absolute, resolved path
|
|
assert_eq!(entries[0].file_type, FileType::Dir);
|
|
assert_eq!(entries[0].path, root_dir.path().canonicalize().unwrap());
|
|
assert_eq!(entries[0].depth, 0);
|
|
|
|
assert_eq!(entries[1].file_type, FileType::File);
|
|
assert_eq!(entries[1].path, Path::new("file1"));
|
|
assert_eq!(entries[1].depth, 1);
|
|
|
|
assert_eq!(entries[2].file_type, FileType::Symlink);
|
|
assert_eq!(entries[2].path, Path::new("link1"));
|
|
assert_eq!(entries[2].depth, 1);
|
|
|
|
assert_eq!(entries[3].file_type, FileType::Dir);
|
|
assert_eq!(entries[3].path, Path::new("sub1"));
|
|
assert_eq!(entries[3].depth, 1);
|
|
}
|
|
x => panic!("Unexpected response: {:?}", x),
|
|
}
|
|
}
|
|
|
|
#[rstest]
|
|
#[tokio::test]
|
|
async fn dir_read_should_support_returning_absolute_paths(#[future] session: Session) {
|
|
let mut session = session.await;
|
|
// Create directory with some nested items
|
|
let root_dir = setup_dir().await;
|
|
|
|
let req = Request::new(
|
|
"test-tenant",
|
|
vec![RequestData::DirRead {
|
|
path: root_dir.path().to_path_buf(),
|
|
depth: 1,
|
|
absolute: true,
|
|
canonicalize: false,
|
|
include_root: false,
|
|
}],
|
|
);
|
|
|
|
let res = session.send(req).await.unwrap();
|
|
assert_eq!(res.payload.len(), 1, "Wrong payload size");
|
|
match &res.payload[0] {
|
|
ResponseData::DirEntries { entries, .. } => {
|
|
assert_eq!(entries.len(), 3, "Wrong number of entries found");
|
|
let root_path = root_dir.path().canonicalize().unwrap();
|
|
|
|
assert_eq!(entries[0].file_type, FileType::File);
|
|
assert_eq!(entries[0].path, root_path.join("file1"));
|
|
assert_eq!(entries[0].depth, 1);
|
|
|
|
assert_eq!(entries[1].file_type, FileType::Symlink);
|
|
assert_eq!(entries[1].path, root_path.join("link1"));
|
|
assert_eq!(entries[1].depth, 1);
|
|
|
|
assert_eq!(entries[2].file_type, FileType::Dir);
|
|
assert_eq!(entries[2].path, root_path.join("sub1"));
|
|
assert_eq!(entries[2].depth, 1);
|
|
}
|
|
x => panic!("Unexpected response: {:?}", x),
|
|
}
|
|
}
|
|
|
|
#[rstest]
|
|
#[tokio::test]
|
|
async fn dir_read_should_support_returning_canonicalized_paths(#[future] session: Session) {
|
|
let mut session = session.await;
|
|
// Create directory with some nested items
|
|
let root_dir = setup_dir().await;
|
|
|
|
let req = Request::new(
|
|
"test-tenant",
|
|
vec![RequestData::DirRead {
|
|
path: root_dir.path().to_path_buf(),
|
|
depth: 1,
|
|
absolute: false,
|
|
canonicalize: true,
|
|
include_root: false,
|
|
}],
|
|
);
|
|
|
|
let res = session.send(req).await.unwrap();
|
|
assert_eq!(res.payload.len(), 1, "Wrong payload size");
|
|
match &res.payload[0] {
|
|
ResponseData::DirEntries { entries, .. } => {
|
|
assert_eq!(entries.len(), 3, "Wrong number of entries found");
|
|
|
|
assert_eq!(entries[0].file_type, FileType::File);
|
|
assert_eq!(entries[0].path, Path::new("file1"));
|
|
assert_eq!(entries[0].depth, 1);
|
|
|
|
assert_eq!(entries[1].file_type, FileType::Dir);
|
|
assert_eq!(entries[1].path, Path::new("sub1"));
|
|
assert_eq!(entries[1].depth, 1);
|
|
|
|
// Symlink should be resolved from $ROOT/link1 -> $ROOT/sub1/file2
|
|
assert_eq!(entries[2].file_type, FileType::Symlink);
|
|
assert_eq!(entries[2].path, Path::new("sub1").join("file2"));
|
|
assert_eq!(entries[2].depth, 1);
|
|
}
|
|
x => panic!("Unexpected response: {:?}", x),
|
|
}
|
|
}
|
|
|
|
#[rstest]
|
|
#[tokio::test]
|
|
async fn dir_create_should_send_error_if_fails(#[future] session: Session) {
|
|
let mut session = session.await;
|
|
// Make a path that has multiple non-existent components
|
|
// so the creation will fail
|
|
let root_dir = setup_dir().await;
|
|
let path = root_dir.path().join("nested").join("new-dir");
|
|
|
|
let req = Request::new(
|
|
"test-tenant",
|
|
vec![RequestData::DirCreate {
|
|
path: path.to_path_buf(),
|
|
all: false,
|
|
}],
|
|
);
|
|
|
|
let res = session.send(req).await.unwrap();
|
|
assert_eq!(res.payload.len(), 1, "Wrong payload size");
|
|
assert!(
|
|
matches!(res.payload[0], ResponseData::Error(_)),
|
|
"Unexpected response: {:?}",
|
|
res.payload[0]
|
|
);
|
|
|
|
// Also verify that the directory was not actually created
|
|
assert!(!path.exists(), "Path unexpectedly exists");
|
|
}
|
|
|
|
#[rstest]
|
|
#[tokio::test]
|
|
async fn dir_create_should_send_ok_when_successful(#[future] session: Session) {
|
|
let mut session = session.await;
|
|
let root_dir = setup_dir().await;
|
|
let path = root_dir.path().join("new-dir");
|
|
|
|
let req = Request::new(
|
|
"test-tenant",
|
|
vec![RequestData::DirCreate {
|
|
path: path.to_path_buf(),
|
|
all: false,
|
|
}],
|
|
);
|
|
|
|
let res = session.send(req).await.unwrap();
|
|
assert_eq!(res.payload.len(), 1, "Wrong payload size");
|
|
assert!(
|
|
matches!(res.payload[0], ResponseData::Ok),
|
|
"Unexpected response: {:?}",
|
|
res.payload[0]
|
|
);
|
|
|
|
// Also verify that the directory was actually created
|
|
assert!(path.exists(), "Directory not created");
|
|
}
|
|
|
|
#[rstest]
|
|
#[tokio::test]
|
|
async fn dir_create_should_support_creating_multiple_dir_components(#[future] session: Session) {
|
|
let mut session = session.await;
|
|
let root_dir = setup_dir().await;
|
|
let path = root_dir.path().join("nested").join("new").join("dir");
|
|
|
|
let req = Request::new(
|
|
"test-tenant",
|
|
vec![RequestData::DirCreate {
|
|
path: path.to_path_buf(),
|
|
all: true,
|
|
}],
|
|
);
|
|
|
|
let res = session.send(req).await.unwrap();
|
|
assert_eq!(res.payload.len(), 1, "Wrong payload size");
|
|
assert!(
|
|
matches!(res.payload[0], ResponseData::Ok),
|
|
"Unexpected response: {:?}",
|
|
res.payload[0]
|
|
);
|
|
|
|
// Also verify that the directory was actually created
|
|
assert!(path.exists(), "Directory not created");
|
|
}
|
|
|
|
#[rstest]
|
|
#[tokio::test]
|
|
async fn remove_should_send_error_on_failure(#[future] session: Session) {
|
|
let mut session = session.await;
|
|
let temp = TempDir::new().unwrap();
|
|
let file = temp.child("missing-file");
|
|
|
|
let req = Request::new(
|
|
"test-tenant",
|
|
vec![RequestData::Remove {
|
|
path: file.path().to_path_buf(),
|
|
force: false,
|
|
}],
|
|
);
|
|
|
|
let res = session.send(req).await.unwrap();
|
|
assert_eq!(res.payload.len(), 1, "Wrong payload size");
|
|
assert!(
|
|
matches!(res.payload[0], ResponseData::Error(_)),
|
|
"Unexpected response: {:?}",
|
|
res.payload[0]
|
|
);
|
|
|
|
// Also, verify that path does not exist
|
|
file.assert(predicate::path::missing());
|
|
}
|
|
|
|
#[rstest]
|
|
#[tokio::test]
|
|
async fn remove_should_support_deleting_a_directory(#[future] session: Session) {
|
|
let mut session = session.await;
|
|
let temp = TempDir::new().unwrap();
|
|
let dir = temp.child("dir");
|
|
dir.create_dir_all().unwrap();
|
|
|
|
let req = Request::new(
|
|
"test-tenant",
|
|
vec![RequestData::Remove {
|
|
path: dir.path().to_path_buf(),
|
|
force: false,
|
|
}],
|
|
);
|
|
|
|
let res = session.send(req).await.unwrap();
|
|
assert_eq!(res.payload.len(), 1, "Wrong payload size");
|
|
assert!(
|
|
matches!(res.payload[0], ResponseData::Ok),
|
|
"Unexpected response: {:?}",
|
|
res.payload[0]
|
|
);
|
|
|
|
// Also, verify that path does not exist
|
|
dir.assert(predicate::path::missing());
|
|
}
|
|
|
|
#[rstest]
|
|
#[tokio::test]
|
|
async fn remove_should_delete_nonempty_directory_if_force_is_true(#[future] session: Session) {
|
|
let mut session = session.await;
|
|
let temp = TempDir::new().unwrap();
|
|
let dir = temp.child("dir");
|
|
dir.create_dir_all().unwrap();
|
|
dir.child("file").touch().unwrap();
|
|
|
|
let req = Request::new(
|
|
"test-tenant",
|
|
vec![RequestData::Remove {
|
|
path: dir.path().to_path_buf(),
|
|
force: true,
|
|
}],
|
|
);
|
|
|
|
let res = session.send(req).await.unwrap();
|
|
assert_eq!(res.payload.len(), 1, "Wrong payload size");
|
|
assert!(
|
|
matches!(res.payload[0], ResponseData::Ok),
|
|
"Unexpected response: {:?}",
|
|
res.payload[0]
|
|
);
|
|
|
|
// Also, verify that path does not exist
|
|
dir.assert(predicate::path::missing());
|
|
}
|
|
|
|
#[rstest]
|
|
#[tokio::test]
|
|
async fn remove_should_support_deleting_a_single_file(#[future] session: Session) {
|
|
let mut session = session.await;
|
|
let temp = TempDir::new().unwrap();
|
|
let file = temp.child("some-file");
|
|
file.touch().unwrap();
|
|
|
|
let req = Request::new(
|
|
"test-tenant",
|
|
vec![RequestData::Remove {
|
|
path: file.path().to_path_buf(),
|
|
force: false,
|
|
}],
|
|
);
|
|
|
|
let res = session.send(req).await.unwrap();
|
|
assert_eq!(res.payload.len(), 1, "Wrong payload size");
|
|
assert!(
|
|
matches!(res.payload[0], ResponseData::Ok),
|
|
"Unexpected response: {:?}",
|
|
res.payload[0]
|
|
);
|
|
|
|
// Also, verify that path does not exist
|
|
file.assert(predicate::path::missing());
|
|
}
|
|
|
|
#[rstest]
|
|
#[tokio::test]
|
|
async fn copy_should_send_error_on_failure(#[future] session: Session) {
|
|
let mut session = session.await;
|
|
let temp = TempDir::new().unwrap();
|
|
let src = temp.child("src");
|
|
let dst = temp.child("dst");
|
|
|
|
let req = Request::new(
|
|
"test-tenant",
|
|
vec![RequestData::Copy {
|
|
src: src.path().to_path_buf(),
|
|
dst: dst.path().to_path_buf(),
|
|
}],
|
|
);
|
|
|
|
let res = session.send(req).await.unwrap();
|
|
assert_eq!(res.payload.len(), 1, "Wrong payload size");
|
|
assert!(
|
|
matches!(res.payload[0], ResponseData::Error(_)),
|
|
"Unexpected response: {:?}",
|
|
res.payload[0]
|
|
);
|
|
|
|
// Also, verify that destination does not exist
|
|
dst.assert(predicate::path::missing());
|
|
}
|
|
|
|
#[rstest]
|
|
#[tokio::test]
|
|
async fn copy_should_support_copying_an_entire_directory(#[future] session: Session) {
|
|
let mut session = session.await;
|
|
let temp = TempDir::new().unwrap();
|
|
|
|
let src = temp.child("src");
|
|
src.create_dir_all().unwrap();
|
|
let src_file = src.child("file");
|
|
src_file.write_str("some contents").unwrap();
|
|
|
|
let dst = temp.child("dst");
|
|
let dst_file = dst.child("file");
|
|
|
|
let req = Request::new(
|
|
"test-tenant",
|
|
vec![RequestData::Copy {
|
|
src: src.path().to_path_buf(),
|
|
dst: dst.path().to_path_buf(),
|
|
}],
|
|
);
|
|
|
|
let res = session.send(req).await.unwrap();
|
|
assert_eq!(res.payload.len(), 1, "Wrong payload size");
|
|
assert!(
|
|
matches!(res.payload[0], ResponseData::Ok),
|
|
"Unexpected response: {:?}",
|
|
res.payload[0]
|
|
);
|
|
|
|
// Verify that we have source and destination directories and associated contents
|
|
src.assert(predicate::path::is_dir());
|
|
src_file.assert(predicate::path::is_file());
|
|
dst.assert(predicate::path::is_dir());
|
|
dst_file.assert(predicate::path::eq_file(src_file.path()));
|
|
}
|
|
|
|
#[rstest]
|
|
#[tokio::test]
|
|
async fn copy_should_support_copying_an_empty_directory(#[future] session: Session) {
|
|
let mut session = session.await;
|
|
let temp = TempDir::new().unwrap();
|
|
let src = temp.child("src");
|
|
src.create_dir_all().unwrap();
|
|
let dst = temp.child("dst");
|
|
|
|
let req = Request::new(
|
|
"test-tenant",
|
|
vec![RequestData::Copy {
|
|
src: src.path().to_path_buf(),
|
|
dst: dst.path().to_path_buf(),
|
|
}],
|
|
);
|
|
|
|
let res = session.send(req).await.unwrap();
|
|
assert_eq!(res.payload.len(), 1, "Wrong payload size");
|
|
assert!(
|
|
matches!(res.payload[0], ResponseData::Ok),
|
|
"Unexpected response: {:?}",
|
|
res.payload[0]
|
|
);
|
|
|
|
// Verify that we still have source and destination directories
|
|
src.assert(predicate::path::is_dir());
|
|
dst.assert(predicate::path::is_dir());
|
|
}
|
|
|
|
#[rstest]
|
|
#[tokio::test]
|
|
async fn copy_should_support_copying_a_directory_that_only_contains_directories(
|
|
#[future] session: Session,
|
|
) {
|
|
let mut session = session.await;
|
|
let temp = TempDir::new().unwrap();
|
|
|
|
let src = temp.child("src");
|
|
src.create_dir_all().unwrap();
|
|
let src_dir = src.child("dir");
|
|
src_dir.create_dir_all().unwrap();
|
|
|
|
let dst = temp.child("dst");
|
|
let dst_dir = dst.child("dir");
|
|
|
|
let req = Request::new(
|
|
"test-tenant",
|
|
vec![RequestData::Copy {
|
|
src: src.path().to_path_buf(),
|
|
dst: dst.path().to_path_buf(),
|
|
}],
|
|
);
|
|
|
|
let res = session.send(req).await.unwrap();
|
|
assert_eq!(res.payload.len(), 1, "Wrong payload size");
|
|
assert!(
|
|
matches!(res.payload[0], ResponseData::Ok),
|
|
"Unexpected response: {:?}",
|
|
res.payload[0]
|
|
);
|
|
|
|
// Verify that we have source and destination directories and associated contents
|
|
src.assert(predicate::path::is_dir().name("src"));
|
|
src_dir.assert(predicate::path::is_dir().name("src/dir"));
|
|
dst.assert(predicate::path::is_dir().name("dst"));
|
|
dst_dir.assert(predicate::path::is_dir().name("dst/dir"));
|
|
}
|
|
|
|
#[rstest]
|
|
#[tokio::test]
|
|
async fn copy_should_support_copying_a_single_file(#[future] session: Session) {
|
|
let mut session = session.await;
|
|
let temp = TempDir::new().unwrap();
|
|
let src = temp.child("src");
|
|
src.write_str("some text").unwrap();
|
|
let dst = temp.child("dst");
|
|
|
|
let req = Request::new(
|
|
"test-tenant",
|
|
vec![RequestData::Copy {
|
|
src: src.path().to_path_buf(),
|
|
dst: dst.path().to_path_buf(),
|
|
}],
|
|
);
|
|
|
|
let res = session.send(req).await.unwrap();
|
|
assert_eq!(res.payload.len(), 1, "Wrong payload size");
|
|
assert!(
|
|
matches!(res.payload[0], ResponseData::Ok),
|
|
"Unexpected response: {:?}",
|
|
res.payload[0]
|
|
);
|
|
|
|
// Verify that we still have source and that destination has source's contents
|
|
src.assert(predicate::path::is_file());
|
|
dst.assert(predicate::path::eq_file(src.path()));
|
|
}
|
|
|
|
#[rstest]
|
|
#[tokio::test]
|
|
async fn rename_should_send_error_on_failure(#[future] session: Session) {
|
|
let mut session = session.await;
|
|
let temp = TempDir::new().unwrap();
|
|
let src = temp.child("src");
|
|
let dst = temp.child("dst");
|
|
|
|
let req = Request::new(
|
|
"test-tenant",
|
|
vec![RequestData::Rename {
|
|
src: src.path().to_path_buf(),
|
|
dst: dst.path().to_path_buf(),
|
|
}],
|
|
);
|
|
|
|
let res = session.send(req).await.unwrap();
|
|
assert_eq!(res.payload.len(), 1, "Wrong payload size");
|
|
assert!(
|
|
matches!(res.payload[0], ResponseData::Error(_)),
|
|
"Unexpected response: {:?}",
|
|
res.payload[0]
|
|
);
|
|
|
|
// Also, verify that destination does not exist
|
|
dst.assert(predicate::path::missing());
|
|
}
|
|
|
|
#[rstest]
|
|
#[tokio::test]
|
|
async fn rename_should_support_renaming_an_entire_directory(#[future] session: Session) {
|
|
let mut session = session.await;
|
|
let temp = TempDir::new().unwrap();
|
|
|
|
let src = temp.child("src");
|
|
src.create_dir_all().unwrap();
|
|
let src_file = src.child("file");
|
|
src_file.write_str("some contents").unwrap();
|
|
|
|
let dst = temp.child("dst");
|
|
let dst_file = dst.child("file");
|
|
|
|
let req = Request::new(
|
|
"test-tenant",
|
|
vec![RequestData::Rename {
|
|
src: src.path().to_path_buf(),
|
|
dst: dst.path().to_path_buf(),
|
|
}],
|
|
);
|
|
|
|
let res = session.send(req).await.unwrap();
|
|
assert_eq!(res.payload.len(), 1, "Wrong payload size");
|
|
assert!(
|
|
matches!(res.payload[0], ResponseData::Ok),
|
|
"Unexpected response: {:?}",
|
|
res.payload[0]
|
|
);
|
|
|
|
// Verify that we moved the contents
|
|
src.assert(predicate::path::missing());
|
|
src_file.assert(predicate::path::missing());
|
|
dst.assert(predicate::path::is_dir());
|
|
dst_file.assert("some contents");
|
|
}
|
|
|
|
#[rstest]
|
|
#[tokio::test]
|
|
async fn rename_should_support_renaming_a_single_file(#[future] session: Session) {
|
|
let mut session = session.await;
|
|
let temp = TempDir::new().unwrap();
|
|
let src = temp.child("src");
|
|
src.write_str("some text").unwrap();
|
|
let dst = temp.child("dst");
|
|
|
|
let req = Request::new(
|
|
"test-tenant",
|
|
vec![RequestData::Rename {
|
|
src: src.path().to_path_buf(),
|
|
dst: dst.path().to_path_buf(),
|
|
}],
|
|
);
|
|
|
|
let res = session.send(req).await.unwrap();
|
|
assert_eq!(res.payload.len(), 1, "Wrong payload size");
|
|
assert!(
|
|
matches!(res.payload[0], ResponseData::Ok),
|
|
"Unexpected response: {:?}",
|
|
res.payload[0]
|
|
);
|
|
|
|
// Verify that we moved the file
|
|
src.assert(predicate::path::missing());
|
|
dst.assert("some text");
|
|
}
|
|
|
|
#[rstest]
|
|
#[tokio::test]
|
|
async fn exists_should_send_true_if_path_exists(#[future] session: Session) {
|
|
let mut session = session.await;
|
|
let temp = TempDir::new().unwrap();
|
|
let file = temp.child("file");
|
|
file.touch().unwrap();
|
|
|
|
let req = Request::new(
|
|
"test-tenant",
|
|
vec![RequestData::Exists {
|
|
path: file.path().to_path_buf(),
|
|
}],
|
|
);
|
|
|
|
let res = session.send(req).await.unwrap();
|
|
assert_eq!(res.payload.len(), 1, "Wrong payload size");
|
|
assert_eq!(res.payload[0], ResponseData::Exists(true));
|
|
}
|
|
|
|
#[rstest]
|
|
#[tokio::test]
|
|
async fn exists_should_send_false_if_path_does_not_exist(#[future] session: Session) {
|
|
let mut session = session.await;
|
|
let temp = TempDir::new().unwrap();
|
|
let file = temp.child("file");
|
|
|
|
let req = Request::new(
|
|
"test-tenant",
|
|
vec![RequestData::Exists {
|
|
path: file.path().to_path_buf(),
|
|
}],
|
|
);
|
|
|
|
let res = session.send(req).await.unwrap();
|
|
assert_eq!(res.payload.len(), 1, "Wrong payload size");
|
|
assert_eq!(res.payload[0], ResponseData::Exists(false));
|
|
}
|
|
|
|
#[rstest]
|
|
#[tokio::test]
|
|
async fn metadata_should_send_error_on_failure(#[future] session: Session) {
|
|
let mut session = session.await;
|
|
let temp = TempDir::new().unwrap();
|
|
let file = temp.child("file");
|
|
|
|
let req = Request::new(
|
|
"test-tenant",
|
|
vec![RequestData::Metadata {
|
|
path: file.path().to_path_buf(),
|
|
canonicalize: false,
|
|
resolve_file_type: false,
|
|
}],
|
|
);
|
|
|
|
let res = session.send(req).await.unwrap();
|
|
assert_eq!(res.payload.len(), 1, "Wrong payload size");
|
|
assert!(
|
|
matches!(res.payload[0], ResponseData::Error(_)),
|
|
"Unexpected response: {:?}",
|
|
res.payload[0]
|
|
);
|
|
}
|
|
|
|
#[rstest]
|
|
#[tokio::test]
|
|
async fn metadata_should_send_back_metadata_on_file_if_exists(#[future] session: Session) {
|
|
let mut session = session.await;
|
|
let temp = TempDir::new().unwrap();
|
|
let file = temp.child("file");
|
|
file.write_str("some text").unwrap();
|
|
|
|
let req = Request::new(
|
|
"test-tenant",
|
|
vec![RequestData::Metadata {
|
|
path: file.path().to_path_buf(),
|
|
canonicalize: false,
|
|
resolve_file_type: false,
|
|
}],
|
|
);
|
|
|
|
let res = session.send(req).await.unwrap();
|
|
assert_eq!(res.payload.len(), 1, "Wrong payload size");
|
|
assert!(
|
|
matches!(
|
|
res.payload[0],
|
|
ResponseData::Metadata {
|
|
canonicalized_path: None,
|
|
file_type: FileType::File,
|
|
len: 9,
|
|
readonly: false,
|
|
..
|
|
}
|
|
),
|
|
"Unexpected response: {:?}",
|
|
res.payload[0]
|
|
);
|
|
}
|
|
|
|
#[rstest]
|
|
#[tokio::test]
|
|
async fn metadata_should_send_back_metadata_on_dir_if_exists(#[future] session: Session) {
|
|
let mut session = session.await;
|
|
let temp = TempDir::new().unwrap();
|
|
let dir = temp.child("dir");
|
|
dir.create_dir_all().unwrap();
|
|
|
|
let req = Request::new(
|
|
"test-tenant",
|
|
vec![RequestData::Metadata {
|
|
path: dir.path().to_path_buf(),
|
|
canonicalize: false,
|
|
resolve_file_type: false,
|
|
}],
|
|
);
|
|
|
|
let res = session.send(req).await.unwrap();
|
|
assert_eq!(res.payload.len(), 1, "Wrong payload size");
|
|
assert!(
|
|
matches!(
|
|
res.payload[0],
|
|
ResponseData::Metadata {
|
|
canonicalized_path: None,
|
|
file_type: FileType::Dir,
|
|
readonly: false,
|
|
..
|
|
}
|
|
),
|
|
"Unexpected response: {:?}",
|
|
res.payload[0]
|
|
);
|
|
}
|
|
|
|
#[rstest]
|
|
#[tokio::test]
|
|
async fn metadata_should_send_back_metadata_on_symlink_if_exists(#[future] session: Session) {
|
|
let mut session = session.await;
|
|
let temp = TempDir::new().unwrap();
|
|
let file = temp.child("file");
|
|
file.write_str("some text").unwrap();
|
|
|
|
let symlink = temp.child("link");
|
|
symlink.symlink_to_file(file.path()).unwrap();
|
|
|
|
let req = Request::new(
|
|
"test-tenant",
|
|
vec![RequestData::Metadata {
|
|
path: symlink.path().to_path_buf(),
|
|
canonicalize: false,
|
|
resolve_file_type: false,
|
|
}],
|
|
);
|
|
|
|
let res = session.send(req).await.unwrap();
|
|
assert_eq!(res.payload.len(), 1, "Wrong payload size");
|
|
assert!(
|
|
matches!(
|
|
res.payload[0],
|
|
ResponseData::Metadata {
|
|
canonicalized_path: None,
|
|
file_type: FileType::Symlink,
|
|
readonly: false,
|
|
..
|
|
}
|
|
),
|
|
"Unexpected response: {:?}",
|
|
res.payload[0]
|
|
);
|
|
}
|
|
|
|
#[rstest]
|
|
#[tokio::test]
|
|
async fn metadata_should_include_canonicalized_path_if_flag_specified(#[future] session: Session) {
|
|
let mut session = session.await;
|
|
let temp = TempDir::new().unwrap();
|
|
let file = temp.child("file");
|
|
file.write_str("some text").unwrap();
|
|
|
|
let symlink = temp.child("link");
|
|
symlink.symlink_to_file(file.path()).unwrap();
|
|
|
|
let req = Request::new(
|
|
"test-tenant",
|
|
vec![RequestData::Metadata {
|
|
path: symlink.path().to_path_buf(),
|
|
canonicalize: true,
|
|
resolve_file_type: false,
|
|
}],
|
|
);
|
|
|
|
let res = session.send(req).await.unwrap();
|
|
assert_eq!(res.payload.len(), 1, "Wrong payload size");
|
|
match &res.payload[0] {
|
|
ResponseData::Metadata {
|
|
canonicalized_path: Some(path),
|
|
file_type: FileType::Symlink,
|
|
readonly: false,
|
|
..
|
|
} => assert_eq!(
|
|
path,
|
|
&file.path().canonicalize().unwrap(),
|
|
"Symlink canonicalized path does not match referenced file"
|
|
),
|
|
x => panic!("Unexpected response: {:?}", x),
|
|
}
|
|
}
|
|
|
|
#[rstest]
|
|
#[tokio::test]
|
|
async fn metadata_should_resolve_file_type_of_symlink_if_flag_specified(
|
|
#[future] session: Session,
|
|
) {
|
|
let mut session = session.await;
|
|
let temp = TempDir::new().unwrap();
|
|
let file = temp.child("file");
|
|
file.write_str("some text").unwrap();
|
|
|
|
let symlink = temp.child("link");
|
|
symlink.symlink_to_file(file.path()).unwrap();
|
|
|
|
let req = Request::new(
|
|
"test-tenant",
|
|
vec![RequestData::Metadata {
|
|
path: symlink.path().to_path_buf(),
|
|
canonicalize: false,
|
|
resolve_file_type: true,
|
|
}],
|
|
);
|
|
|
|
let res = session.send(req).await.unwrap();
|
|
assert_eq!(res.payload.len(), 1, "Wrong payload size");
|
|
match &res.payload[0] {
|
|
ResponseData::Metadata {
|
|
file_type: FileType::File,
|
|
..
|
|
} => {}
|
|
x => panic!("Unexpected response: {:?}", x),
|
|
}
|
|
}
|
|
|
|
#[rstest]
|
|
#[tokio::test]
|
|
async fn proc_run_should_send_error_over_stderr_on_failure(#[future] session: Session) {
|
|
let mut session = session.await;
|
|
let req = Request::new(
|
|
"test-tenant",
|
|
vec![RequestData::ProcRun {
|
|
cmd: DOES_NOT_EXIST_BIN.to_str().unwrap().to_string(),
|
|
args: Vec::new(),
|
|
}],
|
|
);
|
|
|
|
// NOTE: This diverges from distant in that we don't get an error message and instead
|
|
// will always get stderr as ssh runs every command in some kind of shell
|
|
let mut mailbox = session.mail(req).await.unwrap();
|
|
|
|
// Get proc start message
|
|
let res = mailbox.next().await.unwrap();
|
|
assert_eq!(res.payload.len(), 1, "Wrong payload size");
|
|
let proc_id = match &res.payload[0] {
|
|
ResponseData::ProcStart { id } => *id,
|
|
x => panic!("Unexpected response: {:?}", x),
|
|
};
|
|
|
|
// Get proc stderr message
|
|
let res = mailbox.next().await.unwrap();
|
|
assert_eq!(res.payload.len(), 1, "Wrong payload size");
|
|
match &res.payload[0] {
|
|
ResponseData::ProcStderr { id, .. } => {
|
|
assert_eq!(proc_id, *id, "Wrong process stderr received");
|
|
}
|
|
x => panic!("Unexpected response: {:?}", x),
|
|
}
|
|
|
|
// Get proc done message
|
|
let res = mailbox.next().await.unwrap();
|
|
assert_eq!(res.payload.len(), 1, "Wrong payload size");
|
|
match &res.payload[0] {
|
|
ResponseData::ProcDone { id, .. } => {
|
|
assert_eq!(proc_id, *id, "Wrong process done received");
|
|
}
|
|
x => panic!("Unexpected response: {:?}", x),
|
|
}
|
|
}
|
|
|
|
#[rstest]
|
|
#[tokio::test]
|
|
async fn proc_run_should_send_back_proc_start_on_success(#[future] session: Session) {
|
|
let mut session = session.await;
|
|
let req = Request::new(
|
|
"test-tenant",
|
|
vec![RequestData::ProcRun {
|
|
cmd: SCRIPT_RUNNER.to_string(),
|
|
args: vec![ECHO_ARGS_TO_STDOUT_SH.to_str().unwrap().to_string()],
|
|
}],
|
|
);
|
|
|
|
let res = session.send(req).await.unwrap();
|
|
assert_eq!(res.payload.len(), 1, "Wrong payload size");
|
|
assert!(
|
|
matches!(&res.payload[0], ResponseData::ProcStart { .. }),
|
|
"Unexpected response: {:?}",
|
|
res.payload[0]
|
|
);
|
|
}
|
|
|
|
// NOTE: Ignoring on windows because it's using WSL which wants a Linux path
|
|
// with / but thinks it's on windows and is providing \
|
|
#[rstest]
|
|
#[tokio::test]
|
|
#[cfg_attr(windows, ignore)]
|
|
async fn proc_run_should_send_back_stdout_periodically_when_available(#[future] session: Session) {
|
|
let mut session = session.await;
|
|
// Run a program that echoes to stdout
|
|
let req = Request::new(
|
|
"test-tenant",
|
|
vec![RequestData::ProcRun {
|
|
cmd: SCRIPT_RUNNER.to_string(),
|
|
args: vec![
|
|
ECHO_ARGS_TO_STDOUT_SH.to_str().unwrap().to_string(),
|
|
String::from("'some stdout'"),
|
|
],
|
|
}],
|
|
);
|
|
|
|
let mut mailbox = session.mail(req).await.unwrap();
|
|
|
|
let res = mailbox.next().await.unwrap();
|
|
assert_eq!(res.payload.len(), 1, "Wrong payload size");
|
|
assert!(
|
|
matches!(&res.payload[0], ResponseData::ProcStart { .. }),
|
|
"Unexpected response: {:?}",
|
|
res.payload[0]
|
|
);
|
|
|
|
// Gather two additional responses:
|
|
//
|
|
// 1. An indirect response for stdout
|
|
// 2. An indirect response that is proc completing
|
|
//
|
|
// Note that order is not a guarantee, so we have to check that
|
|
// we get one of each type of response
|
|
let res1 = mailbox.next().await.expect("Missing first response");
|
|
let res2 = mailbox.next().await.expect("Missing second response");
|
|
|
|
let mut got_stdout = false;
|
|
let mut got_done = false;
|
|
|
|
let mut check_res = |res: &Response| {
|
|
assert_eq!(res.payload.len(), 1, "Wrong payload size");
|
|
match &res.payload[0] {
|
|
ResponseData::ProcStdout { data, .. } => {
|
|
assert_eq!(data, "some stdout", "Got wrong stdout");
|
|
got_stdout = true;
|
|
}
|
|
ResponseData::ProcDone { success, .. } => {
|
|
assert!(success, "Process should have completed successfully");
|
|
got_done = true;
|
|
}
|
|
x => panic!("Unexpected response: {:?}", x),
|
|
}
|
|
};
|
|
|
|
check_res(&res1);
|
|
check_res(&res2);
|
|
assert!(got_stdout, "Missing stdout response");
|
|
assert!(got_done, "Missing done response");
|
|
}
|
|
|
|
// NOTE: Ignoring on windows because it's using WSL which wants a Linux path
|
|
// with / but thinks it's on windows and is providing \
|
|
#[rstest]
|
|
#[tokio::test]
|
|
#[cfg_attr(windows, ignore)]
|
|
async fn proc_run_should_send_back_stderr_periodically_when_available(#[future] session: Session) {
|
|
let mut session = session.await;
|
|
// Run a program that echoes to stderr
|
|
let req = Request::new(
|
|
"test-tenant",
|
|
vec![RequestData::ProcRun {
|
|
cmd: SCRIPT_RUNNER.to_string(),
|
|
args: vec![
|
|
ECHO_ARGS_TO_STDERR_SH.to_str().unwrap().to_string(),
|
|
String::from("'some stderr'"),
|
|
],
|
|
}],
|
|
);
|
|
|
|
let mut mailbox = session.mail(req).await.unwrap();
|
|
|
|
let res = mailbox.next().await.unwrap();
|
|
assert_eq!(res.payload.len(), 1, "Wrong payload size");
|
|
assert!(
|
|
matches!(&res.payload[0], ResponseData::ProcStart { .. }),
|
|
"Unexpected response: {:?}",
|
|
res.payload[0]
|
|
);
|
|
|
|
// Gather two additional responses:
|
|
//
|
|
// 1. An indirect response for stderr
|
|
// 2. An indirect response that is proc completing
|
|
//
|
|
// Note that order is not a guarantee, so we have to check that
|
|
// we get one of each type of response
|
|
let res1 = mailbox.next().await.expect("Missing first response");
|
|
let res2 = mailbox.next().await.expect("Missing second response");
|
|
|
|
let mut got_stderr = false;
|
|
let mut got_done = false;
|
|
|
|
let mut check_res = |res: &Response| {
|
|
assert_eq!(res.payload.len(), 1, "Wrong payload size");
|
|
match &res.payload[0] {
|
|
ResponseData::ProcStderr { data, .. } => {
|
|
assert_eq!(data, "some stderr", "Got wrong stderr");
|
|
got_stderr = true;
|
|
}
|
|
ResponseData::ProcDone { success, .. } => {
|
|
assert!(success, "Process should have completed successfully");
|
|
got_done = true;
|
|
}
|
|
x => panic!("Unexpected response: {:?}", x),
|
|
}
|
|
};
|
|
|
|
check_res(&res1);
|
|
check_res(&res2);
|
|
assert!(got_stderr, "Missing stderr response");
|
|
assert!(got_done, "Missing done response");
|
|
}
|
|
|
|
// NOTE: Ignoring on windows because it's using WSL which wants a Linux path
|
|
// with / but thinks it's on windows and is providing \
|
|
#[rstest]
|
|
#[tokio::test]
|
|
#[cfg_attr(windows, ignore)]
|
|
async fn proc_run_should_clear_process_from_state_when_done(#[future] session: Session) {
|
|
let mut session = session.await;
|
|
// Run a program that ends after a little bit
|
|
let req = Request::new(
|
|
"test-tenant",
|
|
vec![RequestData::ProcRun {
|
|
cmd: SCRIPT_RUNNER.to_string(),
|
|
args: vec![SLEEP_SH.to_str().unwrap().to_string(), String::from("0.1")],
|
|
}],
|
|
);
|
|
let mut mailbox = session.mail(req).await.unwrap();
|
|
|
|
let res = mailbox.next().await.unwrap();
|
|
assert_eq!(res.payload.len(), 1, "Wrong payload size");
|
|
let id = match &res.payload[0] {
|
|
ResponseData::ProcStart { id } => *id,
|
|
x => panic!("Unexpected response: {:?}", x),
|
|
};
|
|
|
|
// Verify that the state has the process
|
|
let res = session
|
|
.send(Request::new("test-tenant", vec![RequestData::ProcList {}]))
|
|
.await
|
|
.unwrap();
|
|
assert_eq!(res.payload.len(), 1, "Wrong payload size");
|
|
match &res.payload[0] {
|
|
ResponseData::ProcEntries { entries } => assert_eq!(entries[0].id, id),
|
|
x => panic!("Unexpected response: {:?}", x),
|
|
}
|
|
|
|
// Wait for process to finish
|
|
let _ = mailbox.next().await.unwrap();
|
|
|
|
// Verify that the state was cleared
|
|
let res = session
|
|
.send(Request::new("test-tenant", vec![RequestData::ProcList {}]))
|
|
.await
|
|
.unwrap();
|
|
assert_eq!(res.payload.len(), 1, "Wrong payload size");
|
|
match &res.payload[0] {
|
|
ResponseData::ProcEntries { entries } => assert!(entries.is_empty(), "Proc not cleared"),
|
|
x => panic!("Unexpected response: {:?}", x),
|
|
}
|
|
}
|
|
|
|
#[rstest]
|
|
#[tokio::test]
|
|
async fn proc_run_should_clear_process_from_state_when_killed(#[future] session: Session) {
|
|
let mut session = session.await;
|
|
// Run a program that ends slowly
|
|
let req = Request::new(
|
|
"test-tenant",
|
|
vec![RequestData::ProcRun {
|
|
cmd: SCRIPT_RUNNER.to_string(),
|
|
args: vec![SLEEP_SH.to_str().unwrap().to_string(), String::from("1")],
|
|
}],
|
|
);
|
|
|
|
let mut mailbox = session.mail(req).await.unwrap();
|
|
|
|
let res = mailbox.next().await.unwrap();
|
|
assert_eq!(res.payload.len(), 1, "Wrong payload size");
|
|
let id = match &res.payload[0] {
|
|
ResponseData::ProcStart { id } => *id,
|
|
x => panic!("Unexpected response: {:?}", x),
|
|
};
|
|
|
|
// Verify that the state has the process
|
|
let res = session
|
|
.send(Request::new("test-tenant", vec![RequestData::ProcList {}]))
|
|
.await
|
|
.unwrap();
|
|
assert_eq!(res.payload.len(), 1, "Wrong payload size");
|
|
match &res.payload[0] {
|
|
ResponseData::ProcEntries { entries } => assert_eq!(entries[0].id, id),
|
|
x => panic!("Unexpected response: {:?}", x),
|
|
}
|
|
|
|
// Send kill signal
|
|
let req = Request::new("test-tenant", vec![RequestData::ProcKill { id }]);
|
|
let _ = session.send(req).await.unwrap();
|
|
|
|
// Wait for the proc done
|
|
let _ = mailbox.next().await.unwrap();
|
|
|
|
// Verify that the state was cleared
|
|
let res = session
|
|
.send(Request::new("test-tenant", vec![RequestData::ProcList {}]))
|
|
.await
|
|
.unwrap();
|
|
assert_eq!(res.payload.len(), 1, "Wrong payload size");
|
|
match &res.payload[0] {
|
|
ResponseData::ProcEntries { entries } => assert!(entries.is_empty(), "Proc not cleared"),
|
|
x => panic!("Unexpected response: {:?}", x),
|
|
}
|
|
}
|
|
|
|
#[rstest]
|
|
#[tokio::test]
|
|
async fn proc_kill_should_send_error_on_failure(#[future] session: Session) {
|
|
let mut session = session.await;
|
|
// Send kill to a non-existent process
|
|
let req = Request::new(
|
|
"test-tenant",
|
|
vec![RequestData::ProcKill { id: 0xDEADBEEF }],
|
|
);
|
|
|
|
let res = session.send(req).await.unwrap();
|
|
assert_eq!(res.payload.len(), 1, "Wrong payload size");
|
|
|
|
// Verify that we get an error
|
|
assert!(
|
|
matches!(res.payload[0], ResponseData::Error(_)),
|
|
"Unexpected response: {:?}",
|
|
res.payload[0]
|
|
);
|
|
}
|
|
|
|
#[rstest]
|
|
#[tokio::test]
|
|
async fn proc_kill_should_send_ok_and_done_responses_on_success(#[future] session: Session) {
|
|
let mut session = session.await;
|
|
// First, run a program that sits around (sleep for 1 second)
|
|
let req = Request::new(
|
|
"test-tenant",
|
|
vec![RequestData::ProcRun {
|
|
cmd: SCRIPT_RUNNER.to_string(),
|
|
args: vec![SLEEP_SH.to_str().unwrap().to_string(), String::from("1")],
|
|
}],
|
|
);
|
|
|
|
let mut mailbox = session.mail(req).await.unwrap();
|
|
|
|
let res = mailbox.next().await.unwrap();
|
|
assert_eq!(res.payload.len(), 1, "Wrong payload size");
|
|
|
|
// Second, grab the id of the started process
|
|
let id = match &res.payload[0] {
|
|
ResponseData::ProcStart { id } => *id,
|
|
x => panic!("Unexpected response: {:?}", x),
|
|
};
|
|
|
|
// Third, send kill for process
|
|
// NOTE: We cannot let the state get dropped as it results in killing
|
|
// the child process automatically; so, we clone another reference here
|
|
let req = Request::new("test-tenant", vec![RequestData::ProcKill { id }]);
|
|
let res = session.send(req).await.unwrap();
|
|
match &res.payload[0] {
|
|
ResponseData::Ok => {}
|
|
x => panic!("Unexpected response: {:?}", x),
|
|
}
|
|
|
|
// Fourth, verify that the process completes
|
|
let res = mailbox.next().await.unwrap();
|
|
match &res.payload[0] {
|
|
ResponseData::ProcDone { success, .. } => {
|
|
assert!(!success, "Process should not have completed successfully");
|
|
}
|
|
x => panic!("Unexpected response: {:?}", x),
|
|
}
|
|
}
|
|
|
|
#[rstest]
|
|
#[tokio::test]
|
|
async fn proc_stdin_should_send_error_on_failure(#[future] session: Session) {
|
|
let mut session = session.await;
|
|
// Send stdin to a non-existent process
|
|
let req = Request::new(
|
|
"test-tenant",
|
|
vec![RequestData::ProcStdin {
|
|
id: 0xDEADBEEF,
|
|
data: String::from("some input"),
|
|
}],
|
|
);
|
|
|
|
let res = session.send(req).await.unwrap();
|
|
assert_eq!(res.payload.len(), 1, "Wrong payload size");
|
|
|
|
// Verify that we get an error
|
|
assert!(
|
|
matches!(res.payload[0], ResponseData::Error(_)),
|
|
"Unexpected response: {:?}",
|
|
res.payload[0]
|
|
);
|
|
}
|
|
|
|
// NOTE: Ignoring on windows because it's using WSL which wants a Linux path
|
|
// with / but thinks it's on windows and is providing \
|
|
#[rstest]
|
|
#[tokio::test]
|
|
#[cfg_attr(windows, ignore)]
|
|
async fn proc_stdin_should_send_ok_on_success_and_properly_send_stdin_to_process(
|
|
#[future] session: Session,
|
|
) {
|
|
let mut session = session.await;
|
|
|
|
// First, run a program that listens for stdin
|
|
let req = Request::new(
|
|
"test-tenant",
|
|
vec![RequestData::ProcRun {
|
|
cmd: SCRIPT_RUNNER.to_string(),
|
|
args: vec![ECHO_STDIN_TO_STDOUT_SH.to_str().unwrap().to_string()],
|
|
}],
|
|
);
|
|
let mut mailbox = session.mail(req).await.unwrap();
|
|
|
|
let res = mailbox.next().await.unwrap();
|
|
assert_eq!(res.payload.len(), 1, "Wrong payload size");
|
|
|
|
// Second, grab the id of the started process
|
|
let id = match &res.payload[0] {
|
|
ResponseData::ProcStart { id } => *id,
|
|
x => panic!("Unexpected response: {:?}", x),
|
|
};
|
|
|
|
// Third, send stdin to the remote process
|
|
// NOTE: We cannot let the state get dropped as it results in killing
|
|
// the child process; so, we clone another reference here
|
|
let req = Request::new(
|
|
"test-tenant",
|
|
vec![RequestData::ProcStdin {
|
|
id,
|
|
data: String::from("hello world\n"),
|
|
}],
|
|
);
|
|
let res = session.send(req).await.unwrap();
|
|
match &res.payload[0] {
|
|
ResponseData::Ok => {}
|
|
x => panic!("Unexpected response: {:?}", x),
|
|
}
|
|
|
|
// Fourth, gather an indirect response that is stdout from echoing our stdin
|
|
let res = mailbox.next().await.unwrap();
|
|
match &res.payload[0] {
|
|
ResponseData::ProcStdout { data, .. } => {
|
|
assert_eq!(data, "hello world\n", "Mirrored data didn't match");
|
|
}
|
|
x => panic!("Unexpected response: {:?}", x),
|
|
}
|
|
}
|
|
|
|
#[rstest]
|
|
#[tokio::test]
|
|
async fn proc_list_should_send_proc_entry_list(#[future] session: Session) {
|
|
let mut session = session.await;
|
|
let req = Request::new(
|
|
"test-tenant",
|
|
vec![RequestData::ProcRun {
|
|
cmd: SCRIPT_RUNNER.to_string(),
|
|
args: vec![SLEEP_SH.to_str().unwrap().to_string(), String::from("10")],
|
|
}],
|
|
);
|
|
|
|
let res = session.send(req).await.unwrap();
|
|
assert_eq!(res.payload.len(), 1, "Wrong payload size");
|
|
|
|
// Grab the id of the started process
|
|
let id = match &res.payload[0] {
|
|
ResponseData::ProcStart { id } => *id,
|
|
x => panic!("Unexpected response: {:?}", x),
|
|
};
|
|
|
|
let req = Request::new("test-tenant", vec![RequestData::ProcList {}]);
|
|
|
|
let res = session.send(req).await.unwrap();
|
|
assert_eq!(res.payload.len(), 1, "Wrong payload size");
|
|
|
|
// Verify our process shows up in our entry list
|
|
assert_eq!(
|
|
res.payload[0],
|
|
ResponseData::ProcEntries {
|
|
entries: vec![RunningProcess {
|
|
cmd: SCRIPT_RUNNER.to_string(),
|
|
args: vec![SLEEP_SH.to_str().unwrap().to_string(), String::from("10")],
|
|
id,
|
|
}],
|
|
},
|
|
"Unexpected response: {:?}",
|
|
res.payload[0]
|
|
);
|
|
}
|
|
|
|
#[rstest]
|
|
#[tokio::test]
|
|
async fn system_info_should_send_system_info_based_on_binary(#[future] session: Session) {
|
|
let mut session = session.await;
|
|
|
|
// Figure out what SFTP's realpath(.) would resolve to
|
|
let res = session
|
|
.send(Request::new(
|
|
"test-tenant",
|
|
vec![RequestData::Metadata {
|
|
path: PathBuf::from("."),
|
|
canonicalize: true,
|
|
resolve_file_type: false,
|
|
}],
|
|
))
|
|
.await
|
|
.unwrap();
|
|
let current_dir = if let ResponseData::Metadata {
|
|
canonicalized_path, ..
|
|
} = &res.payload[0]
|
|
{
|
|
canonicalized_path
|
|
.as_deref()
|
|
.expect("Missing canonicalized path")
|
|
.to_path_buf()
|
|
} else {
|
|
panic!("Failed to get metadata for '.'")
|
|
};
|
|
|
|
let req = Request::new("test-tenant", vec![RequestData::SystemInfo {}]);
|
|
let res = session.send(req).await.unwrap();
|
|
assert_eq!(res.payload.len(), 1, "Wrong payload size");
|
|
assert_eq!(
|
|
res.payload[0],
|
|
ResponseData::SystemInfo {
|
|
family: env::consts::FAMILY.to_string(),
|
|
os: "".to_string(),
|
|
arch: "".to_string(),
|
|
current_dir,
|
|
main_separator: std::path::MAIN_SEPARATOR,
|
|
},
|
|
"Unexpected response: {:?}",
|
|
res.payload[0]
|
|
);
|
|
}
|