sheeter-engine 0.12.0

A CLI tool for converting data files between formats. v0.1.0 supports CSV <-> JSON.
Documentation
use assert_cmd::Command;
use sheeter_engine::{convert_workbook_with_options, Format, SqlDialect};
use std::fs;
use tempfile::tempdir;

fn run_binary(
    args: &[&str],
    input_bytes: &[u8],
    in_ext: &str,
    out_ext: &str,
) -> Result<Vec<u8>, String> {
    let dir = tempdir().unwrap();
    let inp = dir.path().join(format!("in.{in_ext}"));
    let out = dir.path().join(format!("out.{out_ext}"));
    fs::write(&inp, input_bytes).unwrap();
    let mut cmd = Command::cargo_bin("sheeter").unwrap();
    cmd.args(args);
    cmd.arg(inp.to_str().unwrap());
    cmd.arg(out.to_str().unwrap());
    let result = cmd.output().unwrap();
    if !result.status.success() {
        return Err(String::from_utf8_lossy(&result.stderr).to_string());
    }
    Ok(fs::read(out).unwrap())
}

#[test]
fn csv_to_ods_roundtrip() {
    let csv = "name,age\nAlice,30\nBob,25\n";
    let ods = run_binary(
        &["convert", "--from", "csv", "--to", "ods"],
        csv.as_bytes(),
        "csv",
        "ods",
    )
    .unwrap();
    assert!(ods.starts_with(b"PK\x03\x04"), "ODS must be a ZIP");
    let back = run_binary(
        &["convert", "--from", "ods", "--to", "csv"],
        &ods,
        "ods",
        "csv",
    )
    .unwrap();
    let back_str = String::from_utf8(back).unwrap();
    assert!(back_str.contains("Alice"));
    assert!(back_str.contains("30"));
    assert!(back_str.contains("Bob"));
}

#[test]
fn ods_preserves_number_types() {
    let csv = "id,value\n1,3.14\n2,42\n";
    let ods = run_binary(
        &["convert", "--from", "csv", "--to", "ods"],
        csv.as_bytes(),
        "csv",
        "ods",
    )
    .unwrap();
    let json = run_binary(
        &["convert", "--from", "ods", "--to", "json"],
        &ods,
        "ods",
        "json",
    )
    .unwrap();
    let json_str = String::from_utf8(json).unwrap();
    assert!(json_str.contains("3.14"), "float preserved: {json_str}");
    assert!(json_str.contains("42"), "int preserved: {json_str}");
}

#[test]
fn ods_multi_sheet_to_csv_n_outputs() {
    // Build via CLI directly: two separate CSV-to-ODS, then we only test single.
    // Multi-sheet ODS write is covered by canonical tests; CLI multi-sheet
    // output already covered in xlsx tests.
    let dir = tempdir().unwrap();
    let inp = dir.path().join("in.csv");
    let out = dir.path().join("out.ods");
    fs::write(&inp, "a,b\n1,2\n").unwrap();
    Command::cargo_bin("sheeter")
        .unwrap()
        .args([
            "convert",
            "--from",
            "csv",
            "--to",
            "ods",
            inp.to_str().unwrap(),
            out.to_str().unwrap(),
        ])
        .assert()
        .success();
    assert!(out.exists());
    assert!(fs::read(&out).unwrap().starts_with(b"PK\x03\x04"));
}

#[test]
fn ods_to_json_via_hub() {
    let csv = "name\nAlice\nBob\n";
    let ods = run_binary(
        &["convert", "--from", "csv", "--to", "ods"],
        csv.as_bytes(),
        "csv",
        "ods",
    )
    .unwrap();
    let json = run_binary(
        &["convert", "--from", "ods", "--to", "json"],
        &ods,
        "ods",
        "json",
    )
    .unwrap();
    let parsed: serde_json::Value = serde_json::from_slice(&json).unwrap();
    let arr = parsed.as_array().expect("array");
    assert_eq!(arr.len(), 2);
}

#[test]
fn ods_detected_by_content() {
    let csv = "name\nAlice\n";
    let ods = run_binary(
        &["convert", "--from", "csv", "--to", "ods"],
        csv.as_bytes(),
        "csv",
        "ods",
    )
    .unwrap();

    // Write to stdin-less file and run detect
    let dir = tempdir().unwrap();
    let path = dir.path().join("mystery.bin");
    fs::write(&path, &ods).unwrap();
    let output = Command::cargo_bin("sheeter")
        .unwrap()
        .args(["detect", path.to_str().unwrap()])
        .output()
        .unwrap();
    assert!(output.status.success());
    assert_eq!(String::from_utf8_lossy(&output.stdout).trim(), "Ods");
}

#[test]
fn ods_api_roundtrip() {
    let csv = "x,y\n1,a\n2,b\n";
    let hub = sheeter_engine::CanonicalValue::from_csv(csv).unwrap();
    let ods = sheeter_engine::to_ods_workbook(&[("Sheet1".to_string(), hub)]).unwrap();
    let parsed = sheeter_engine::CanonicalValue::from_ods(&ods).unwrap();
    let json = parsed.to_json().unwrap();
    assert!(json.contains("\"1\""), "1 preserved: {json}");
    assert!(json.contains("\"a\""), "a preserved: {json}");
}

#[test]
fn ods_workbook_via_convert_workbook() {
    let csv = "n\n1\n2\n3\n";
    let ods = convert_workbook_with_options(
        Format::Csv,
        Format::Ods,
        csv.as_bytes(),
        SqlDialect::Generic,
        None,
    )
    .unwrap();
    assert_eq!(ods.len(), 1);
    assert!(ods[0].1.starts_with(b"PK\x03\x04"));
}