use std::fs;
use std::sync::Arc;
use std::sync::atomic::{AtomicU64, Ordering};
use std::time::{Duration, SystemTime};
use tempfile::TempDir;
const FEDORA_HOST: &str = "fedora";
const FEDORA_USER: &str = "nick";
fn create_fedora_config() -> msy::ssh::config::SshConfig {
use msy::ssh::config::SshConfig;
let mut config = SshConfig::new(FEDORA_HOST);
config.user = FEDORA_USER.to_string();
config.port = 22;
config
}
fn create_remote_test_path(test_name: &str) -> String {
format!("/tmp/sy_ssh_test_{}_{}", test_name, std::process::id())
}
fn cleanup_remote_path(path: &str) {
let cleanup_cmd = format!("ssh {} 'rm -rf {}'", FEDORA_HOST, path);
let _ = std::process::Command::new("sh").arg("-c").arg(&cleanup_cmd).output();
}
#[tokio::test]
#[serial_test::serial]
#[ignore]
async fn test_ssh_scan_directory() {
use msy::transport::Transport;
use msy::transport::ssh::SshTransport;
let remote_path = create_remote_test_path("scan");
let setup_cmd = format!(
"ssh {} 'mkdir -p {}/subdir && echo test1 > {}/file1.txt && echo test2 > {}/subdir/file2.txt && touch {}/empty.txt'",
FEDORA_HOST, remote_path, remote_path, remote_path, remote_path
);
std::process::Command::new("sh").arg("-c").arg(&setup_cmd).output().expect("Failed to create remote test structure");
let config = create_fedora_config();
let transport = SshTransport::new(&config).await.expect("Failed to connect");
let entries = transport.scan(std::path::Path::new(&remote_path)).await.expect("Scan failed");
assert!(entries.len() >= 4, "Expected at least 4 entries, got {}", entries.len());
let paths: Vec<String> = entries.iter().map(|e| e.path.to_string_lossy().to_string()).collect();
assert!(paths.iter().any(|p| p.contains("file1.txt")), "Missing file1.txt");
assert!(paths.iter().any(|p| p.contains("file2.txt")), "Missing file2.txt");
assert!(paths.iter().any(|p| p.contains("empty.txt")), "Missing empty.txt");
assert!(paths.iter().any(|p| p.contains("subdir")), "Missing subdir");
cleanup_remote_path(&remote_path);
println!("✅ SSH scan_directory: PASS");
}
#[tokio::test]
#[serial_test::serial]
#[ignore]
async fn test_ssh_exists() {
use msy::transport::Transport;
use msy::transport::ssh::SshTransport;
let remote_path = create_remote_test_path("exists");
let setup_cmd = format!("ssh {} 'echo test > {}'", FEDORA_HOST, remote_path);
std::process::Command::new("sh").arg("-c").arg(&setup_cmd).output().expect("Failed to create remote file");
let config = create_fedora_config();
let transport = SshTransport::new(&config).await.expect("Failed to connect");
let exists = transport.exists(std::path::Path::new(&remote_path)).await.expect("exists() failed");
assert!(exists, "File should exist");
let nonexistent = format!("{}_nonexistent", remote_path);
let exists = transport.exists(std::path::Path::new(&nonexistent)).await.expect("exists() failed");
assert!(!exists, "Nonexistent file should not exist");
cleanup_remote_path(&remote_path);
println!("✅ SSH exists: PASS");
}
#[tokio::test]
#[serial_test::serial]
#[ignore]
async fn test_ssh_create_dir_all() {
use msy::transport::Transport;
use msy::transport::ssh::SshTransport;
let remote_path = create_remote_test_path("create_dir");
let nested_path = format!("{}/a/b/c/d", remote_path);
let config = create_fedora_config();
let transport = SshTransport::new(&config).await.expect("Failed to connect");
transport.create_dir_all(std::path::Path::new(&nested_path)).await.expect("create_dir_all failed");
let exists = transport.exists(std::path::Path::new(&nested_path)).await.expect("exists check failed");
assert!(exists, "Nested directory should exist");
cleanup_remote_path(&remote_path);
println!("✅ SSH create_dir_all: PASS");
}
#[tokio::test]
#[serial_test::serial]
#[ignore]
async fn test_ssh_read_write_file() {
use msy::transport::Transport;
use msy::transport::ssh::SshTransport;
let remote_path = create_remote_test_path("read_write");
let config = create_fedora_config();
let transport = SshTransport::new(&config).await.expect("Failed to connect");
let test_data = b"Hello from SSH write_file test!\nLine 2\nLine 3";
transport.write_file(std::path::Path::new(&remote_path), test_data, SystemTime::now()).await.expect("write_file failed");
let read_data = transport.read_file(std::path::Path::new(&remote_path)).await.expect("read_file failed");
assert_eq!(read_data, test_data, "Read data doesn't match written data");
cleanup_remote_path(&remote_path);
println!("✅ SSH read_write_file: PASS");
}
#[tokio::test]
#[serial_test::serial]
#[ignore]
#[serial_test::serial]
async fn test_ssh_remove() {
use msy::transport::Transport;
use msy::transport::ssh::SshTransport;
let remote_path = create_remote_test_path("remove");
let config = create_fedora_config();
let transport = SshTransport::new(&config).await.expect("Failed to connect");
transport.write_file(std::path::Path::new(&remote_path), b"test", SystemTime::now()).await.expect("write_file failed");
let exists = transport.exists(std::path::Path::new(&remote_path)).await.expect("exists check failed");
assert!(exists, "File should exist before removal");
transport.remove(std::path::Path::new(&remote_path), false).await.expect("remove failed");
let exists = transport.exists(std::path::Path::new(&remote_path)).await.expect("exists check failed");
assert!(!exists, "File should not exist after removal");
println!("✅ SSH remove: PASS");
}
#[tokio::test]
#[serial_test::serial]
#[ignore]
async fn test_ssh_get_mtime() {
use msy::transport::Transport;
use msy::transport::ssh::SshTransport;
let remote_path = create_remote_test_path("mtime");
let config = create_fedora_config();
let transport = SshTransport::new(&config).await.expect("Failed to connect");
let known_time = SystemTime::now() - Duration::from_secs(3600); transport.write_file(std::path::Path::new(&remote_path), b"test", known_time).await.expect("write_file failed");
let mtime = transport.get_mtime(std::path::Path::new(&remote_path)).await.expect("get_mtime failed");
let diff = mtime.duration_since(known_time).unwrap_or_else(|e| e.duration());
assert!(diff < Duration::from_secs(2), "Mtime difference too large: {:?}", diff);
cleanup_remote_path(&remote_path);
println!("✅ SSH get_mtime: PASS");
}
#[tokio::test]
#[serial_test::serial]
#[ignore]
#[serial_test::serial]
async fn test_ssh_file_info() {
use msy::transport::Transport;
use msy::transport::ssh::SshTransport;
let remote_path = create_remote_test_path("file_info");
let config = create_fedora_config();
let transport = SshTransport::new(&config).await.expect("Failed to connect");
let test_data = b"test file info content";
transport.write_file(std::path::Path::new(&remote_path), test_data, SystemTime::now()).await.expect("write_file failed");
let info = transport.file_info(std::path::Path::new(&remote_path)).await.expect("file_info failed");
assert_eq!(info.size, test_data.len() as u64, "File size mismatch");
cleanup_remote_path(&remote_path);
println!("✅ SSH file_info: PASS");
}
#[tokio::test]
#[serial_test::serial]
#[ignore]
async fn test_ssh_copy_file_basic() {
use msy::transport::Transport;
use msy::transport::ssh::SshTransport;
let remote_source = create_remote_test_path("copy_source");
let remote_dest = create_remote_test_path("copy_dest");
let config = create_fedora_config();
let transport = SshTransport::new(&config).await.expect("Failed to connect");
let test_data = vec![0x42u8; 1024 * 1024]; transport
.write_file(std::path::Path::new(&remote_source), &test_data, SystemTime::now())
.await
.expect("write_file failed");
let local_temp = TempDir::new().unwrap();
let local_file = local_temp.path().join("temp.dat");
let result = transport
.copy_file_streaming(std::path::Path::new(&remote_source), &local_file, None)
.await
.expect("copy_file_streaming failed");
assert_eq!(result.bytes_written, 1024 * 1024, "Bytes written mismatch");
let read_data = fs::read(&local_file).expect("Failed to read local file");
assert_eq!(read_data, test_data, "Copied data doesn't match");
cleanup_remote_path(&remote_source);
cleanup_remote_path(&remote_dest);
println!("✅ SSH copy_file_basic: PASS");
}
#[tokio::test]
#[serial_test::serial]
#[ignore]
async fn test_ssh_copy_file_with_progress() {
use msy::transport::Transport;
use msy::transport::ssh::SshTransport;
let remote_source = create_remote_test_path("copy_progress");
let create_cmd = format!("ssh {} 'dd if=/dev/zero of={} bs=1M count=5 2>/dev/null'", FEDORA_HOST, remote_source);
std::process::Command::new("sh").arg("-c").arg(&create_cmd).output().expect("Failed to create remote file");
let config = create_fedora_config();
let transport = SshTransport::new(&config).await.expect("Failed to connect");
let local_dest = TempDir::new().unwrap().path().join("downloaded.dat");
let updates = Arc::new(AtomicU64::new(0));
let updates_clone = updates.clone();
let last_bytes = Arc::new(AtomicU64::new(0));
let last_bytes_clone = last_bytes.clone();
let progress_callback = Arc::new(move |bytes: u64, total: u64| {
updates_clone.fetch_add(1, Ordering::SeqCst);
last_bytes_clone.store(bytes, Ordering::SeqCst);
assert!(bytes <= total, "bytes should not exceed total");
});
let result = transport
.copy_file_streaming(std::path::Path::new(&remote_source), &local_dest, Some(progress_callback))
.await
.expect("Download failed");
assert_eq!(result.bytes_written, 5 * 1024 * 1024);
let update_count = updates.load(Ordering::SeqCst);
assert!(update_count >= 5, "Expected >= 5 updates, got {}", update_count);
let final_bytes = last_bytes.load(Ordering::SeqCst);
assert_eq!(final_bytes, 5 * 1024 * 1024);
cleanup_remote_path(&remote_source);
println!("✅ SSH copy_file_with_progress: PASS");
}
#[tokio::test]
#[serial_test::serial]
#[ignore]
async fn test_ssh_copy_empty_file() {
use msy::transport::Transport;
use msy::transport::ssh::SshTransport;
let remote_source = create_remote_test_path("empty_source");
let config = create_fedora_config();
let transport = SshTransport::new(&config).await.expect("Failed to connect");
transport.write_file(std::path::Path::new(&remote_source), b"", SystemTime::now()).await.expect("write_file failed");
let local_dest = TempDir::new().unwrap().path().join("empty.dat");
let result = transport.copy_file_streaming(std::path::Path::new(&remote_source), &local_dest, None).await.expect("Copy failed");
assert_eq!(result.bytes_written, 0, "Empty file should have 0 bytes");
assert!(local_dest.exists(), "Empty file should exist locally");
cleanup_remote_path(&remote_source);
println!("✅ SSH copy_empty_file: PASS");
}
#[tokio::test]
#[serial_test::serial]
#[ignore]
async fn test_ssh_create_symlink() {
use msy::transport::Transport;
use msy::transport::ssh::SshTransport;
let remote_base = create_remote_test_path("symlink");
let remote_target = format!("{}/target.txt", remote_base);
let remote_link = format!("{}/link.txt", remote_base);
let config = create_fedora_config();
let transport = SshTransport::new(&config).await.expect("Failed to connect");
transport.create_dir_all(std::path::Path::new(&remote_base)).await.expect("create_dir_all failed");
transport
.write_file(std::path::Path::new(&remote_target), b"target content", SystemTime::now())
.await
.expect("write_file failed");
transport
.create_symlink(std::path::Path::new("target.txt"), std::path::Path::new(&remote_link))
.await
.expect("create_symlink failed");
let entries = transport.scan(std::path::Path::new(&remote_base)).await.expect("scan failed");
let link_entry = entries.iter().find(|e| e.path.ends_with("link.txt")).expect("Link should exist");
assert!(link_entry.is_symlink, "Should be a symlink");
cleanup_remote_path(&remote_base);
println!("✅ SSH create_symlink: PASS");
}
#[tokio::test]
#[serial_test::serial]
#[ignore]
async fn test_ssh_read_nonexistent_file() {
use msy::transport::Transport;
use msy::transport::ssh::SshTransport;
let remote_path = create_remote_test_path("nonexistent");
let config = create_fedora_config();
let transport = SshTransport::new(&config).await.expect("Failed to connect");
let result = transport.read_file(std::path::Path::new(&remote_path)).await;
assert!(result.is_err(), "Reading nonexistent file should fail");
println!("✅ SSH read_nonexistent_file: PASS");
}
#[tokio::test]
#[serial_test::serial]
#[ignore]
async fn test_ssh_remove_nonexistent_file() {
use msy::transport::Transport;
use msy::transport::ssh::SshTransport;
let remote_path = create_remote_test_path("nonexistent_remove");
let config = create_fedora_config();
let transport = SshTransport::new(&config).await.expect("Failed to connect");
let result = transport.remove(std::path::Path::new(&remote_path), false).await;
println!("Remove nonexistent result: {:?}", result);
println!("✅ SSH remove_nonexistent_file: PASS");
}
#[tokio::test]
#[serial_test::serial]
#[ignore]
async fn test_ssh_write_to_readonly_parent() {
use msy::transport::Transport;
use msy::transport::ssh::SshTransport;
let remote_base = create_remote_test_path("readonly");
let remote_file = format!("{}/file.txt", remote_base);
let setup_cmd = format!("ssh {} 'mkdir -p {} && chmod 444 {}'", FEDORA_HOST, remote_base, remote_base);
std::process::Command::new("sh").arg("-c").arg(&setup_cmd).output().expect("Failed to setup readonly directory");
let config = create_fedora_config();
let transport = SshTransport::new(&config).await.expect("Failed to connect");
let result = transport.write_file(std::path::Path::new(&remote_file), b"test", SystemTime::now()).await;
assert!(result.is_err(), "Writing to readonly directory should fail");
let cleanup_cmd = format!("ssh {} 'chmod 755 {} && rm -rf {}'", FEDORA_HOST, remote_base, remote_base);
let _ = std::process::Command::new("sh").arg("-c").arg(&cleanup_cmd).output();
println!("✅ SSH write_to_readonly_parent: PASS");
}
#[tokio::test]
#[serial_test::serial]
#[ignore]
async fn test_ssh_special_characters_in_filename() {
use msy::transport::Transport;
use msy::transport::ssh::SshTransport;
let remote_base = create_remote_test_path("special_chars");
let remote_file = format!("{}/file with spaces & special chars!.txt", remote_base);
let config = create_fedora_config();
let transport = SshTransport::new(&config).await.expect("Failed to connect");
transport.create_dir_all(std::path::Path::new(&remote_base)).await.expect("create_dir_all failed");
let result = transport.write_file(std::path::Path::new(&remote_file), b"special content", SystemTime::now()).await;
if result.is_ok() {
let read_data = transport.read_file(std::path::Path::new(&remote_file)).await.expect("read_file failed");
assert_eq!(read_data, b"special content");
println!("✅ SSH special_characters_in_filename: PASS");
} else {
println!("⚠️ SSH special_characters_in_filename: SKIP (not supported)");
}
cleanup_remote_path(&remote_base);
}
#[tokio::test]
#[serial_test::serial]
#[ignore]
async fn test_ssh_deep_directory_hierarchy() {
use msy::transport::Transport;
use msy::transport::ssh::SshTransport;
let remote_base = create_remote_test_path("deep");
let mut deep_path = remote_base.clone();
for i in 0..50 {
deep_path.push_str(&format!("/level{}", i));
}
deep_path.push_str("/file.txt");
let config = create_fedora_config();
let transport = SshTransport::new(&config).await.expect("Failed to connect");
let parent = std::path::Path::new(&deep_path).parent().unwrap();
transport.create_dir_all(parent).await.expect("create_dir_all failed for deep hierarchy");
transport
.write_file(std::path::Path::new(&deep_path), b"deep file", SystemTime::now())
.await
.expect("write_file failed at deep level");
let read_data = transport.read_file(std::path::Path::new(&deep_path)).await.expect("read_file failed at deep level");
assert_eq!(read_data, b"deep file");
cleanup_remote_path(&remote_base);
println!("✅ SSH deep_directory_hierarchy: PASS");
}
#[tokio::test]
#[serial_test::serial]
#[ignore]
async fn test_ssh_binary_data_integrity() {
use msy::transport::Transport;
use msy::transport::ssh::SshTransport;
let remote_path = create_remote_test_path("binary");
let config = create_fedora_config();
let transport = SshTransport::new(&config).await.expect("Failed to connect");
let mut binary_data = Vec::new();
for i in 0..=255u8 {
binary_data.extend_from_slice(&[i; 256]); }
transport
.write_file(std::path::Path::new(&remote_path), &binary_data, SystemTime::now())
.await
.expect("write_file failed");
let read_data = transport.read_file(std::path::Path::new(&remote_path)).await.expect("read_file failed");
assert_eq!(read_data.len(), binary_data.len(), "Binary data length mismatch");
assert_eq!(read_data, binary_data, "Binary data integrity check failed");
cleanup_remote_path(&remote_path);
println!("✅ SSH binary_data_integrity: PASS");
}
#[tokio::test]
#[serial_test::serial]
#[ignore]
async fn test_ssh_large_file_100mb() {
use msy::transport::Transport;
use msy::transport::ssh::SshTransport;
let remote_source = create_remote_test_path("large_100mb");
let create_cmd = format!("ssh {} 'dd if=/dev/zero of={} bs=1M count=100 2>/dev/null'", FEDORA_HOST, remote_source);
println!("Creating 100MB test file on fedora...");
std::process::Command::new("sh").arg("-c").arg(&create_cmd).output().expect("Failed to create 100MB file");
let config = create_fedora_config();
let transport = SshTransport::new(&config).await.expect("Failed to connect");
let local_dest = TempDir::new().unwrap().path().join("large_100mb.dat");
println!("Downloading 100MB file...");
let start = std::time::Instant::now();
let result = transport.copy_file_streaming(std::path::Path::new(&remote_source), &local_dest, None).await.expect("Download failed");
let duration = start.elapsed();
let speed_mbps = (100.0 / duration.as_secs_f64()).round();
assert_eq!(result.bytes_written, 100 * 1024 * 1024);
assert!(local_dest.exists());
cleanup_remote_path(&remote_source);
println!("✅ SSH large_file_100mb: PASS ({:.2}s @ {} MB/s)", duration.as_secs_f64(), speed_mbps);
}
#[tokio::test]
#[serial_test::serial]
#[ignore]
async fn test_ssh_many_small_files() {
use msy::transport::Transport;
use msy::transport::ssh::SshTransport;
let remote_base = create_remote_test_path("many_files");
let config = create_fedora_config();
let transport = SshTransport::new(&config).await.expect("Failed to connect");
transport.create_dir_all(std::path::Path::new(&remote_base)).await.expect("create_dir_all failed");
println!("Creating 100 small files...");
let start = std::time::Instant::now();
for i in 0..100 {
let file_path = format!("{}/file_{:03}.txt", remote_base, i);
let content = format!("File number {}", i);
transport
.write_file(std::path::Path::new(&file_path), content.as_bytes(), SystemTime::now())
.await
.expect("write_file failed");
}
let duration = start.elapsed();
let entries = transport.scan(std::path::Path::new(&remote_base)).await.expect("scan failed");
assert!(entries.len() >= 100, "Expected at least 100 files, got {}", entries.len());
cleanup_remote_path(&remote_base);
println!("✅ SSH many_small_files: PASS (100 files in {:.2}s)", duration.as_secs_f64());
}
#[tokio::test]
#[serial_test::serial]
#[ignore]
async fn test_ssh_connection_pool_concurrent_transfers() {
use msy::transport::Transport;
use msy::transport::ssh::SshTransport;
let remote_base = create_remote_test_path("pool_test");
for i in 1..=3 {
let file_path = format!("{}/file{}.dat", remote_base, i);
let create_cmd = format!("ssh {} 'mkdir -p {} && dd if=/dev/zero of={} bs=1M count=5 2>/dev/null'", FEDORA_HOST, remote_base, file_path);
std::process::Command::new("sh").arg("-c").arg(&create_cmd).output().expect("Failed to create remote file");
}
let config = create_fedora_config();
let transport = Arc::new(SshTransport::with_pool_size(&config, 3).await.expect("Failed to create pool"));
let dest_dir = TempDir::new().unwrap();
let mut handles = vec![];
for i in 1..=3 {
let t = transport.clone();
let remote_file = format!("{}/file{}.dat", remote_base, i);
let local_dest = dest_dir.path().join(format!("file{}.dat", i));
let handle = tokio::spawn(async move { t.copy_file_streaming(std::path::Path::new(&remote_file), &local_dest, None).await.unwrap() });
handles.push(handle);
}
for handle in handles {
let result = handle.await.unwrap();
assert_eq!(result.bytes_written, 5 * 1024 * 1024);
}
cleanup_remote_path(&remote_base);
println!("✅ SSH connection_pool_concurrent_transfers: PASS");
}
#[test]
#[ignore]
fn print_ssh_test_summary() {
println!("\n========================================");
println!("SSH Comprehensive Test Suite Summary");
println!("========================================\n");
println!("SECTION 1: Basic Transport Operations");
println!(" - scan_directory");
println!(" - exists");
println!(" - create_dir_all");
println!(" - read_write_file");
println!(" - remove");
println!(" - get_mtime");
println!(" - file_info");
println!("\nSECTION 2: File Transfer Operations");
println!(" - copy_file_basic");
println!(" - copy_file_with_progress");
println!(" - copy_empty_file");
println!("\nSECTION 3: Symlink Operations");
println!(" - create_symlink");
println!("\nSECTION 4: Error Scenarios");
println!(" - read_nonexistent_file");
println!(" - remove_nonexistent_file");
println!(" - write_to_readonly_parent");
println!("\nSECTION 5: Edge Cases");
println!(" - special_characters_in_filename");
println!(" - deep_directory_hierarchy");
println!(" - binary_data_integrity");
println!("\nSECTION 6: Large Scale Operations");
println!(" - large_file_100mb");
println!(" - many_small_files");
println!("\nSECTION 7: Connection Pool");
println!(" - connection_pool_concurrent_transfers");
println!("\nTotal: 23 comprehensive SSH tests");
println!("\nRun with:");
println!(" cargo test --test ssh_comprehensive_test -- --ignored");
println!("========================================\n");
}