use std::io::Write as _;
use std::os::unix::net::UnixStream;
use std::path::PathBuf;
use std::thread;
use super::{Receiver, ScpRecvOptions, ScpSendOptions, Sender};
fn fresh_tmp(label: &str) -> PathBuf {
use std::sync::atomic::{AtomicU32, Ordering};
static COUNTER: AtomicU32 = AtomicU32::new(0);
let n = COUNTER.fetch_add(1, Ordering::SeqCst);
let pid = std::process::id();
let dir = std::env::temp_dir().join(format!("puressh-scp-test-{}-{}-{}", label, pid, n));
std::fs::create_dir_all(&dir).expect("tmp dir");
dir
}
struct DirGuard(PathBuf);
impl Drop for DirGuard {
fn drop(&mut self) {
let _ = std::fs::remove_dir_all(&self.0);
}
}
#[test]
fn round_trip_single_file() {
let src_dir = fresh_tmp("send");
let _g1 = DirGuard(src_dir.clone());
let dst_dir = fresh_tmp("recv");
let _g2 = DirGuard(dst_dir.clone());
let src_path = src_dir.join("hello.txt");
let payload = b"hello, scp world!\n";
std::fs::write(&src_path, payload).unwrap();
let (a, b) = UnixStream::pair().expect("socketpair");
let dst_dir_thread = dst_dir.clone();
let recv = thread::spawn(move || {
let mut r = Receiver::new(
b,
&dst_dir_thread,
ScpRecvOptions {
recursive: false,
preserve_times: false,
target_is_file: false,
},
)
.expect("recv new");
r.run().expect("recv run");
});
let mut sender = Sender::new(a).expect("send new");
sender
.send_path(&src_path, &ScpSendOptions::default())
.expect("send file");
drop(sender); recv.join().expect("recv join");
let got = std::fs::read(dst_dir.join("hello.txt")).expect("read dst");
assert_eq!(got, payload);
}
#[test]
fn round_trip_directory_tree() {
let src_dir = fresh_tmp("send-tree");
let _g1 = DirGuard(src_dir.clone());
let dst_dir = fresh_tmp("recv-tree");
let _g2 = DirGuard(dst_dir.clone());
let tree = src_dir.join("tree");
std::fs::create_dir_all(tree.join("a/b")).unwrap();
std::fs::write(tree.join("top.txt"), b"top").unwrap();
std::fs::write(tree.join("a/middle.txt"), b"middle").unwrap();
std::fs::write(tree.join("a/b/deep.txt"), b"deep").unwrap();
let (a, b) = UnixStream::pair().expect("socketpair");
let dst_dir_thread = dst_dir.clone();
let recv = thread::spawn(move || {
let mut r = Receiver::new(
b,
&dst_dir_thread,
ScpRecvOptions {
recursive: true,
preserve_times: false,
target_is_file: false,
},
)
.expect("recv new");
r.run().expect("recv run");
});
let mut sender = Sender::new(a).expect("send new");
let opts = ScpSendOptions {
recursive: true,
preserve_times: false,
};
sender.send_path(&tree, &opts).expect("send tree");
drop(sender);
recv.join().expect("recv join");
assert_eq!(std::fs::read(dst_dir.join("tree/top.txt")).unwrap(), b"top");
assert_eq!(
std::fs::read(dst_dir.join("tree/a/middle.txt")).unwrap(),
b"middle"
);
assert_eq!(
std::fs::read(dst_dir.join("tree/a/b/deep.txt")).unwrap(),
b"deep"
);
}
#[test]
fn round_trip_preserve_times() {
let src_dir = fresh_tmp("send-t");
let _g1 = DirGuard(src_dir.clone());
let dst_dir = fresh_tmp("recv-t");
let _g2 = DirGuard(dst_dir.clone());
let src_path = src_dir.join("t.txt");
std::fs::write(&src_path, b"hello").unwrap();
let (a, b) = UnixStream::pair().expect("socketpair");
let dst_dir_thread = dst_dir.clone();
let recv = thread::spawn(move || {
let mut r = Receiver::new(
b,
&dst_dir_thread,
ScpRecvOptions {
recursive: false,
preserve_times: true,
target_is_file: false,
},
)
.expect("recv new");
r.run().expect("recv run");
});
let mut sender = Sender::new(a).expect("send new");
let opts = ScpSendOptions {
recursive: false,
preserve_times: true,
};
sender.send_path(&src_path, &opts).expect("send");
drop(sender);
recv.join().expect("recv join");
assert_eq!(std::fs::read(dst_dir.join("t.txt")).unwrap(), b"hello");
}
#[test]
fn receiver_refuses_directory_without_recursive() {
let src_dir = fresh_tmp("send-nor");
let _g1 = DirGuard(src_dir.clone());
let dst_dir = fresh_tmp("recv-nor");
let _g2 = DirGuard(dst_dir.clone());
let tree = src_dir.join("noR");
std::fs::create_dir(&tree).unwrap();
std::fs::write(tree.join("f"), b"x").unwrap();
let (a, b) = UnixStream::pair().expect("socketpair");
let dst_dir_thread = dst_dir.clone();
let recv = thread::spawn(move || {
let mut r = Receiver::new(
b,
&dst_dir_thread,
ScpRecvOptions::default(), )
.expect("recv new");
r.run() });
let mut sender = Sender::new(a).expect("send new");
let opts = ScpSendOptions {
recursive: true,
..Default::default()
};
let _ = sender.send_path(&tree, &opts);
drop(sender);
let r = recv.join().expect("recv join");
assert!(r.is_err());
}
#[test]
fn validate_rejects_leading_dash() {
use super::protocol::{validate_name, ScpError};
assert!(matches!(validate_name("-rf"), Err(ScpError::BadName(_))));
}
#[test]
fn round_trip_empty_file() {
let src_dir = fresh_tmp("send-empty");
let _g1 = DirGuard(src_dir.clone());
let dst_dir = fresh_tmp("recv-empty");
let _g2 = DirGuard(dst_dir.clone());
let src_path = src_dir.join("empty.txt");
std::fs::File::create(&src_path).unwrap().flush().unwrap();
let (a, b) = UnixStream::pair().expect("socketpair");
let dst_dir_thread = dst_dir.clone();
let recv = thread::spawn(move || {
let mut r = Receiver::new(b, &dst_dir_thread, ScpRecvOptions::default()).expect("recv new");
r.run().expect("recv run");
});
let mut sender = Sender::new(a).expect("send new");
sender
.send_path(&src_path, &ScpSendOptions::default())
.expect("send empty");
drop(sender);
recv.join().expect("recv join");
let got = std::fs::read(dst_dir.join("empty.txt")).expect("read dst");
assert_eq!(got, Vec::<u8>::new());
}