tomlsmith-cli 0.4.0

Command-line adapter for TomlSmith
Documentation
use std::io::Cursor;

use tomlsmith_cli::{ExitStatus, run};

#[test]
fn check_reports_invalid_stdin_and_returns_content_failure() {
    let mut stdin = Cursor::new(b"broken\n".to_vec());
    let mut stdout = Vec::new();
    let mut stderr = Vec::new();

    let status = run(
        ["tomlsmith", "check", "-"],
        &mut stdin,
        &mut stdout,
        &mut stderr,
    );

    assert_eq!(status, ExitStatus::ContentFailure);
    assert!(stdout.is_empty());
    let stderr = String::from_utf8(stderr).expect("diagnostics should be UTF-8");
    assert!(stderr.contains("parse.missing-equals"), "{stderr:?}");
}

#[test]
fn invalid_utf8_is_a_content_diagnostic_at_the_cli_boundary() {
    let mut stdin = Cursor::new(vec![b'a', b' ', b'=', b' ', 0xff, b'\n']);
    let mut stdout = Vec::new();
    let mut stderr = Vec::new();

    let status = run(["tomlsmith", "check"], &mut stdin, &mut stdout, &mut stderr);

    assert_eq!(status, ExitStatus::ContentFailure);
    assert!(stdout.is_empty());
    let stderr = String::from_utf8(stderr).expect("diagnostics should be UTF-8");
    assert!(stderr.contains("parse.invalid-utf8"), "{stderr:?}");
    assert!(stderr.contains("4..5"), "{stderr:?}");

    let mut stdin = Cursor::new(vec![0xff]);
    let mut stdout = Vec::new();
    let mut stderr = Vec::new();
    let status = run(["tomlsmith", "parse"], &mut stdin, &mut stdout, &mut stderr);
    assert_eq!(status, ExitStatus::ContentFailure);
    assert!(stderr.is_empty());
    let output: serde_json::Value =
        serde_json::from_slice(&stdout).expect("invalid UTF-8 parse output should be JSON");
    assert_eq!(output["valid"], false);
    assert_eq!(output["diagnostics"][0]["code"], "parse.invalid-utf8");
    assert_eq!(output["diagnostics"][0]["range"]["start"], 0);
    assert_eq!(output["diagnostics"][0]["range"]["end"], 1);
}

#[test]
fn check_defaults_to_stdin_and_is_quiet_for_valid_toml() {
    let mut stdin = Cursor::new(b"name = \"TomlSmith\"\n".to_vec());
    let mut stdout = Vec::new();
    let mut stderr = Vec::new();

    let status = run(["tomlsmith", "check"], &mut stdin, &mut stdout, &mut stderr);

    assert_eq!(status, ExitStatus::Success);
    assert!(stdout.is_empty());
    assert!(stderr.is_empty());
}

#[test]
fn parse_emits_json_and_honors_explicit_toml_version() {
    let mut stdin = Cursor::new(b"name = \"TomlSmith\"\n".to_vec());
    let mut stdout = Vec::new();
    let mut stderr = Vec::new();

    let status = run(
        ["tomlsmith", "parse", "--toml-version", "1.0"],
        &mut stdin,
        &mut stdout,
        &mut stderr,
    );

    assert_eq!(status, ExitStatus::Success);
    assert!(stderr.is_empty());
    let output: serde_json::Value =
        serde_json::from_slice(&stdout).expect("parse output should be JSON");
    assert_eq!(output["tomlVersion"], "1.0");
    assert_eq!(output["valid"], true);
    assert_eq!(output["diagnostics"], serde_json::json!([]));
}

#[test]
fn parse_keeps_json_on_stdout_when_the_document_is_invalid() {
    let mut stdin = Cursor::new(b"broken\n".to_vec());
    let mut stdout = Vec::new();
    let mut stderr = Vec::new();

    let status = run(["tomlsmith", "parse"], &mut stdin, &mut stdout, &mut stderr);

    assert_eq!(status, ExitStatus::ContentFailure);
    assert!(stderr.is_empty());
    let output: serde_json::Value =
        serde_json::from_slice(&stdout).expect("invalid parse output should still be JSON");
    assert_eq!(output["tomlVersion"], "1.1");
    assert_eq!(output["valid"], false);
    let missing_equals = output["diagnostics"]
        .as_array()
        .expect("diagnostics should be an array")
        .iter()
        .find(|diagnostic| diagnostic["code"] == "parse.missing-equals")
        .expect("the stable missing-equals diagnostic should be present");
    assert_eq!(missing_equals["severity"], "error");
    assert_eq!(missing_equals["range"]["start"], 6);
    assert_eq!(missing_equals["range"]["end"], 6);
}

#[test]
fn missing_input_is_an_operational_failure() {
    let mut stdin = Cursor::new(Vec::new());
    let mut stdout = Vec::new();
    let mut stderr = Vec::new();

    let status = run(
        [
            "tomlsmith",
            "check",
            "/a/path/that/does/not/exist/tomlsmith.toml",
        ],
        &mut stdin,
        &mut stdout,
        &mut stderr,
    );

    assert_eq!(status, ExitStatus::OperationalFailure);
    assert!(stdout.is_empty());
    let stderr = String::from_utf8(stderr).expect("errors should be UTF-8");
    assert!(stderr.starts_with("tomlsmith: "), "{stderr:?}");
}

#[test]
fn fmt_formats_stdin_to_stdout() {
    let mut stdin = Cursor::new(b"name=\"TomlSmith\"\n".to_vec());
    let mut stdout = Vec::new();
    let mut stderr = Vec::new();

    let status = run(["tomlsmith", "fmt"], &mut stdin, &mut stdout, &mut stderr);

    assert_eq!(status, ExitStatus::Success);
    assert_eq!(stdout, b"name = \"TomlSmith\"\n");
    assert!(stderr.is_empty());
}

#[test]
fn fmt_refuses_invalid_stdin_without_emitting_rewritten_text() {
    let mut stdin = Cursor::new(b"broken\n".to_vec());
    let mut stdout = Vec::new();
    let mut stderr = Vec::new();

    let status = run(["tomlsmith", "fmt"], &mut stdin, &mut stdout, &mut stderr);

    assert_eq!(status, ExitStatus::ContentFailure);
    assert!(stdout.is_empty());
    let stderr = String::from_utf8(stderr).expect("diagnostics should be UTF-8");
    assert!(stderr.contains("parse.missing-equals"), "{stderr:?}");
}

#[test]
fn fmt_flags_reach_the_core_format_options() {
    let mut stdin = Cursor::new(b"v = [11111111, 22222222, 33333333]\n".to_vec());
    let mut stdout = Vec::new();
    let mut stderr = Vec::new();

    let status = run(
        [
            "tomlsmith",
            "fmt",
            "--line-width",
            "20",
            "--line-ending",
            "lf",
            "-",
        ],
        &mut stdin,
        &mut stdout,
        &mut stderr,
    );

    assert_eq!(status, ExitStatus::Success);
    let stdout = String::from_utf8(stdout).expect("formatted text should be UTF-8");
    assert!(
        stdout.lines().count() > 1,
        "a narrow --line-width must wrap the array: {stdout:?}"
    );
}

#[test]
fn fmt_check_names_the_input_that_needs_reformatting() {
    let mut stdin = Cursor::new(b"a=1\n".to_vec());
    let mut stdout = Vec::new();
    let mut stderr = Vec::new();

    let status = run(
        ["tomlsmith", "fmt", "--check", "-"],
        &mut stdin,
        &mut stdout,
        &mut stderr,
    );

    assert_eq!(status, ExitStatus::ContentFailure);
    assert!(stdout.is_empty());
    let stderr = String::from_utf8(stderr).expect("messages should be UTF-8");
    assert!(stderr.contains("would reformat stdin"), "{stderr:?}");
}

#[test]
fn fmt_rewrites_files_in_place_without_leaving_temporary_files() {
    let directory = std::env::temp_dir().join(format!("tomlsmith-cli-test-{}", std::process::id()));
    std::fs::create_dir_all(&directory).expect("temporary directory");
    let path = directory.join("input.toml");
    std::fs::write(&path, "a=1\n").expect("seed file");

    let mut stdin = Cursor::new(Vec::new());
    let mut stdout = Vec::new();
    let mut stderr = Vec::new();
    let status = run(
        ["tomlsmith", "fmt", path.to_str().expect("UTF-8 path")],
        &mut stdin,
        &mut stdout,
        &mut stderr,
    );

    assert_eq!(status, ExitStatus::Success);
    assert_eq!(
        std::fs::read_to_string(&path).expect("formatted file"),
        "a = 1\n"
    );
    let leftovers = std::fs::read_dir(&directory)
        .expect("list directory")
        .filter_map(Result::ok)
        .filter(|entry| entry.file_name() != "input.toml")
        .count();
    assert_eq!(leftovers, 0, "no temporary files may remain");
    let _ = std::fs::remove_dir_all(&directory);
}