rustfs-cli 0.1.30

A Rust S3 CLI client for S3-compatible object storage
Documentation
use std::path::{Path, PathBuf};
use std::process::{Command, Output};

fn rc_binary() -> PathBuf {
    if let Ok(path) = std::env::var("CARGO_BIN_EXE_rc") {
        return PathBuf::from(path);
    }

    PathBuf::from(env!("CARGO_MANIFEST_DIR"))
        .parent()
        .expect("cli crate has parent directory")
        .parent()
        .expect("workspace root exists")
        .join("target/debug/rc")
}

fn run_rc(config_dir: &Path, args: &[&str]) -> Output {
    Command::new(rc_binary())
        .env("RC_CONFIG_DIR", config_dir)
        .current_dir(config_dir)
        .args(args)
        .output()
        .expect("run rc")
}

#[test]
fn directional_aliases_treat_slash_paths_on_the_local_side_as_local() {
    let sandbox = tempfile::tempdir().expect("create sandbox");

    let get = run_rc(
        sandbox.path(),
        &[
            "get",
            "unconfigured-alias/reports/report.txt",
            "downloads/report.txt",
        ],
    );
    assert_eq!(get.status.code(), Some(5));
    assert!(String::from_utf8_lossy(&get.stderr).contains("Alias 'unconfigured-alias' not found"));

    let put = run_rc(
        sandbox.path(),
        &[
            "put",
            "uploads/report.txt",
            "unconfigured-alias/reports/report.txt",
        ],
    );
    assert_eq!(put.status.code(), Some(5));
    assert!(String::from_utf8_lossy(&put.stderr).contains("Source not found: uploads/report.txt"));
}

#[test]
fn put_uses_the_same_missing_source_behavior_as_cp() {
    let sandbox = tempfile::tempdir().expect("create sandbox");
    let missing = sandbox.path().join("missing-source.txt");
    let source = missing.to_string_lossy().into_owned();
    let args = [source.as_str(), "unconfigured-alias/reports/report.txt"];

    let cp = run_rc(sandbox.path(), &["cp", args[0], args[1]]);
    let put = run_rc(sandbox.path(), &["put", args[0], args[1]]);

    assert_eq!(put.status.code(), cp.status.code());
    assert_eq!(put.stdout, cp.stdout);
    assert_eq!(put.stderr, cp.stderr);
    assert_eq!(put.status.code(), Some(5));
}

#[test]
fn get_uses_the_same_missing_alias_behavior_as_cp() {
    let sandbox = tempfile::tempdir().expect("create sandbox");
    let target = sandbox.path().join("report.txt");
    let target = target.to_string_lossy().into_owned();
    let args = ["unconfigured-alias/reports/report.txt", target.as_str()];

    let cp = run_rc(sandbox.path(), &["cp", args[0], args[1]]);
    let get = run_rc(sandbox.path(), &["get", args[0], args[1]]);

    assert_eq!(get.status.code(), cp.status.code());
    assert_eq!(get.stdout, cp.stdout);
    assert_eq!(get.stderr, cp.stderr);
    assert_eq!(get.status.code(), Some(5));
}

#[test]
fn direction_mismatches_fail_with_usage_before_remote_access() {
    let sandbox = tempfile::tempdir().expect("create sandbox");
    let local_source = sandbox.path().join("source.txt");
    std::fs::write(&local_source, "payload").expect("write local source");
    let local_target = sandbox.path().join("target.txt");
    let source = local_source.to_string_lossy().into_owned();
    let target = local_target.to_string_lossy().into_owned();

    let get = run_rc(sandbox.path(), &["get", source.as_str(), target.as_str()]);
    assert_eq!(get.status.code(), Some(2));
    assert!(
        String::from_utf8_lossy(&get.stderr)
            .contains("get requires exactly one remote source and one local target")
    );

    let put = run_rc(
        sandbox.path(),
        &[
            "put",
            "unconfigured-alias/reports/report.txt",
            target.as_str(),
        ],
    );
    assert_eq!(put.status.code(), Some(2));
    assert!(
        String::from_utf8_lossy(&put.stderr)
            .contains("put requires one or more local sources and one remote target")
    );
}