yaml-rt-cli 0.1.2

Presentation-preserving command-line YAML query and editor
Documentation
use std::fs;
use std::io::Write;
use std::path::PathBuf;
use std::process::{Command, Stdio};
use std::sync::atomic::{AtomicU64, Ordering};

static COUNTER: AtomicU64 = AtomicU64::new(0);

fn temp_dir() -> PathBuf {
    let counter = COUNTER.fetch_add(1, Ordering::Relaxed);
    let path =
        std::env::temp_dir().join(format!("yaml-rt-cli-test-{}-{counter}", std::process::id()));
    fs::create_dir(&path).unwrap();
    path
}

#[test]
fn help_and_version_report_distribution_metadata() {
    let version = Command::new(env!("CARGO_BIN_EXE_yaml-rt"))
        .arg("--version")
        .output()
        .unwrap();
    assert!(version.status.success(), "{:?}", version.stderr);
    assert_eq!(
        version.stdout,
        format!("yaml-rt {}\n", env!("CARGO_PKG_VERSION")).as_bytes()
    );
    assert!(version.stderr.is_empty());

    let help = Command::new(env!("CARGO_BIN_EXE_yaml-rt"))
        .arg("--help")
        .output()
        .unwrap();
    assert!(help.status.success(), "{:?}", help.stderr);
    assert!(String::from_utf8_lossy(&help.stdout).contains("Usage: yaml-rt"));
    assert!(help.stderr.is_empty());
}

#[test]
fn value_is_never_autodetected_as_a_filename() {
    let directory = temp_dir();
    let input = directory.join("document.yaml");
    let coincidental = directory.join("config.yaml");
    fs::write(&input, "filename: old\n").unwrap();
    fs::write(&coincidental, "from-file\n").unwrap();

    let output = Command::new(env!("CARGO_BIN_EXE_yaml-rt"))
        .current_dir(&directory)
        .args([
            "replace",
            "/filename",
            "--value",
            "config.yaml",
            "document.yaml",
        ])
        .output()
        .unwrap();
    assert!(output.status.success(), "{:?}", output.stderr);
    assert_eq!(output.stdout, b"filename: config.yaml\n");

    fs::remove_dir_all(directory).unwrap();
}

#[test]
fn value_file_and_document_selection_work() {
    let directory = temp_dir();
    let input = directory.join("document.yaml");
    let value = directory.join("value.yaml");
    fs::write(&input, "---\nname: first\n---\nname: second\n").unwrap();
    fs::write(&value, "{nested: true}\n").unwrap();

    let output = Command::new(env!("CARGO_BIN_EXE_yaml-rt"))
        .args([
            "replace",
            "/name",
            "--doc",
            "1",
            "--value-file",
            value.to_str().unwrap(),
            input.to_str().unwrap(),
        ])
        .output()
        .unwrap();
    assert!(output.status.success(), "{:?}", output.stderr);
    assert_eq!(
        output.stdout,
        b"---\nname: first\n---\nname: {nested: true}\n"
    );
    fs::remove_dir_all(directory).unwrap();
}

#[test]
fn query_supports_document_selection_and_output_files() {
    let directory = temp_dir();
    let input = directory.join("document.yaml");
    let result = directory.join("result.txt");
    fs::write(
        &input,
        "---\nusers: [{name: first}]\n---\nusers: [{name: second}]\n",
    )
    .unwrap();

    let output = Command::new(env!("CARGO_BIN_EXE_yaml-rt"))
        .args([
            "query",
            "$.users[*].name",
            "--doc",
            "1",
            "--output",
            result.to_str().unwrap(),
            input.to_str().unwrap(),
        ])
        .output()
        .unwrap();
    assert!(output.status.success(), "{:?}", output.stderr);
    assert!(output.stdout.is_empty());
    assert_eq!(
        fs::read(&result).unwrap(),
        b"\"/users/0/name\": \"second\"\n"
    );
    fs::remove_dir_all(directory).unwrap();
}

#[test]
fn stdin_mutation_and_failed_test_have_expected_streams() {
    let mut child = Command::new(env!("CARGO_BIN_EXE_yaml-rt"))
        .args(["add", "/port", "--value", "8080"])
        .stdin(Stdio::piped())
        .stdout(Stdio::piped())
        .stderr(Stdio::piped())
        .spawn()
        .unwrap();
    child
        .stdin
        .take()
        .unwrap()
        .write_all(b"host: localhost\n")
        .unwrap();
    let output = child.wait_with_output().unwrap();
    assert!(output.status.success(), "{:?}", output.stderr);
    assert_eq!(output.stdout, b"host: localhost\nport: 8080\n");

    let directory = temp_dir();
    let input = directory.join("document.yaml");
    fs::write(&input, "port: 8080\n").unwrap();
    let output = Command::new(env!("CARGO_BIN_EXE_yaml-rt"))
        .args(["test", "/port", "--value", "9090", input.to_str().unwrap()])
        .output()
        .unwrap();
    assert!(!output.status.success());
    assert!(output.stdout.is_empty());
    assert!(String::from_utf8_lossy(&output.stderr).contains("test failed"));
    fs::remove_dir_all(directory).unwrap();
}

#[test]
fn in_place_replaces_only_after_success() {
    let directory = temp_dir();
    let input = directory.join("document.yaml");
    fs::write(&input, "port: 8080\n").unwrap();

    let output = Command::new(env!("CARGO_BIN_EXE_yaml-rt"))
        .args([
            "replace",
            "--in-place",
            "/missing",
            "--value",
            "9090",
            input.to_str().unwrap(),
        ])
        .output()
        .unwrap();
    assert!(!output.status.success());
    assert_eq!(fs::read(&input).unwrap(), b"port: 8080\n");

    let output = Command::new(env!("CARGO_BIN_EXE_yaml-rt"))
        .args([
            "replace",
            "--in-place",
            "/port",
            "--value",
            "9090",
            input.to_str().unwrap(),
        ])
        .output()
        .unwrap();
    assert!(output.status.success(), "{:?}", output.stderr);
    assert!(output.stdout.is_empty());
    assert_eq!(fs::read(&input).unwrap(), b"port: 9090\n");
    fs::remove_dir_all(directory).unwrap();
}