rustfs-cli 0.1.36

A Rust S3 CLI client for S3-compatible object storage
Documentation
#![cfg(not(windows))]

mod admin_support;

use admin_support::{rc_binary, rc_host_alias, start_admin_sequence_test_server};
use serde_json::{Value, json};
use std::process::Command;
use std::time::Duration;

#[test]
fn catalog_binary_paginates_signed_requests_and_redacts_credentials() {
    let config = tempfile::tempdir().unwrap();
    let (endpoint, requests, server) = start_admin_sequence_test_server(vec![
        (
            "200 OK",
            r#"{"identifiers":[{"name":"one"}],"next-page-token":"a+/="}"#,
        ),
        (
            "200 OK",
            r#"{"identifiers":[{"name":"two"}],"storage-credentials":[{"secret-access-key":"hidden"}]}"#,
        ),
    ]);
    let output = Command::new(rc_binary())
        .args([
            "table",
            "list",
            "myalias/warehouse.bucket/sales.eu",
            "--json",
        ])
        .env("RC_CONFIG_DIR", config.path())
        .env("RC_HOST_myalias", rc_host_alias(&endpoint))
        .env("NO_PROXY", "*")
        .env("no_proxy", "*")
        .output()
        .unwrap();
    assert!(
        output.status.success(),
        "{}",
        String::from_utf8_lossy(&output.stderr)
    );
    let data: Value = serde_json::from_slice(&output.stdout).unwrap();
    assert_eq!(data["type"], "table_catalog");
    assert_eq!(data["data"]["operation"], "table_list");
    assert_eq!(
        data["data"]["result"]["identifiers"],
        json!([{"name":"one"},{"name":"two"}])
    );
    assert!(!String::from_utf8_lossy(&output.stdout).contains("hidden"));
    let first = requests.recv_timeout(Duration::from_secs(5)).unwrap();
    let second = requests.recv_timeout(Duration::from_secs(5)).unwrap();
    assert_eq!(
        first.target,
        "/iceberg/v1/warehouse.bucket/namespaces/sales%1Feu/tables?pageSize=1000"
    );
    assert!(
        first
            .headers
            .to_ascii_lowercase()
            .contains("authorization: aws4-hmac-sha256")
    );
    assert!(second.target.ends_with("pageToken=a%2B%2F%3D"));
    server.join().unwrap();
}

#[test]
fn catalog_binary_conflict_preserves_pointer_guards_and_exit_code() {
    let config = tempfile::tempdir().unwrap();
    let file = config.path().join("commit.json");
    std::fs::write(&file, r#"{"new-metadata-location":"s3://warehouse/m2"}"#).unwrap();
    let (endpoint, requests, server) = start_admin_sequence_test_server(vec![(
        "409 Conflict",
        r#"{"error":{"message":"stale metadata"}}"#,
    )]);
    let output = Command::new(rc_binary())
        .args(["table", "commit", "myalias/warehouse/ns/table", "--file"])
        .arg(file)
        .args([
            "--expected-version-token",
            "v1",
            "--expected-metadata-location",
            "s3://warehouse/m1",
            "--commit-id",
            "attempt-1",
            "--json",
        ])
        .env("RC_CONFIG_DIR", config.path())
        .env("RC_HOST_myalias", rc_host_alias(&endpoint))
        .env("NO_PROXY", "*")
        .env("no_proxy", "*")
        .output()
        .unwrap();
    assert_eq!(output.status.code(), Some(6));
    assert!(output.stdout.is_empty());
    let data: Value = serde_json::from_slice(&output.stderr).unwrap();
    assert_eq!(data["status"], "error");
    assert_eq!(data["error"]["retryable"], false);
    let request = requests.recv_timeout(Duration::from_secs(5)).unwrap();
    assert_eq!(request.method, "POST");
    let body: Value = serde_json::from_slice(&request.body).unwrap();
    assert_eq!(body["expected-version-token"], "v1");
    assert_eq!(body["expected-metadata-location"], "s3://warehouse/m1");
    assert_eq!(body["commit-id"], "attempt-1");
    server.join().unwrap();
    assert!(requests.try_recv().is_err());
}

#[test]
fn catalog_binary_missing_alias_uses_catalog_error_envelope() {
    let config = tempfile::tempdir().unwrap();
    let output = Command::new(rc_binary())
        .args([
            "table",
            "show",
            "missing-catalog-alias/warehouse/ns/table",
            "--json",
        ])
        .env("RC_CONFIG_DIR", config.path())
        .env_remove("RC_HOST_missing-catalog-alias")
        .output()
        .unwrap();
    assert_eq!(output.status.code(), Some(5));
    assert!(output.stdout.is_empty());
    let data: Value = serde_json::from_slice(&output.stderr).unwrap();
    assert_eq!(data["type"], "table_catalog");
    assert_eq!(data["error"]["type"], "not_found");
}