use assert_cmd::Command;
use sheeter_engine::{detect_content, detect_extension, CanonicalValue, Format};
use std::fs;
use std::path::Path;
use tempfile::tempdir;
fn hub(json: &str) -> CanonicalValue {
CanonicalValue::from_json(json).unwrap()
}
#[test]
fn roundtrip_csv_parquet_csv() {
let dir = tempdir().unwrap();
let inp = dir.path().join("in.csv");
let mid = dir.path().join("mid.parquet");
let out = dir.path().join("out.csv");
fs::write(&inp, "name,age\nAlice,30\nBob,25\n").unwrap();
Command::cargo_bin("sheeter")
.unwrap()
.args([
"convert",
"--from",
"csv",
"--to",
"parquet",
inp.to_str().unwrap(),
mid.to_str().unwrap(),
])
.assert()
.success();
assert!(mid.exists());
Command::cargo_bin("sheeter")
.unwrap()
.args([
"convert",
"--from",
"parquet",
"--to",
"csv",
mid.to_str().unwrap(),
out.to_str().unwrap(),
])
.assert()
.success();
let csv = fs::read_to_string(out).unwrap();
assert_eq!(csv, "name,age\nAlice,30\nBob,25\n");
}
#[test]
fn roundtrip_json_parquet_json_types() {
let json = r#"[{"i":1,"f":2.5,"b":true,"s":"x"},{"i":2,"f":3.5,"b":false,"s":"y"}]"#;
let bytes = sheeter_engine::to_parquet(&hub(json)).unwrap();
let back = sheeter_engine::from_parquet(&bytes).unwrap();
let parsed: serde_json::Value = serde_json::from_str(&back.to_json().unwrap()).unwrap();
let arr = parsed.as_array().unwrap();
assert_eq!(arr[0]["i"], 1);
assert_eq!(arr[0]["f"], 2.5);
assert_eq!(arr[0]["b"], true);
assert_eq!(arr[0]["s"], "x");
assert_eq!(arr[1]["i"], 2);
assert_eq!(arr[1]["f"], 3.5);
assert_eq!(arr[1]["b"], false);
assert_eq!(arr[1]["s"], "y");
}
#[test]
fn null_values_preserved() {
let json = r#"[{"a":1,"b":null},{"a":2,"b":"x"}]"#;
let bytes = sheeter_engine::to_parquet(&hub(json)).unwrap();
let back = sheeter_engine::from_parquet(&bytes).unwrap();
let parsed: serde_json::Value = serde_json::from_str(&back.to_json().unwrap()).unwrap();
let arr = parsed.as_array().unwrap();
assert_eq!(arr[0]["b"], serde_json::Value::Null);
assert_eq!(arr[1]["b"], "x");
}
#[test]
fn u64_overflow_falls_back_to_utf8() {
let big = u64::MAX;
let json = format!(r#"[{{"n":{}}}]"#, big);
let bytes = sheeter_engine::to_parquet(&hub(&json)).unwrap();
let back = sheeter_engine::from_parquet(&bytes).unwrap();
let parsed: serde_json::Value = serde_json::from_str(&back.to_json().unwrap()).unwrap();
let arr = parsed.as_array().unwrap();
assert_eq!(arr[0]["n"], serde_json::Value::String(big.to_string()));
}
#[test]
fn empty_array_to_parquet_errors() {
let empty = CanonicalValue::Array(vec![]);
assert!(sheeter_engine::to_parquet(&empty).is_err());
}
#[test]
fn parquet_magic_detected() {
let bytes = sheeter_engine::to_parquet(&hub(r#"[{"a":1}]"#)).unwrap();
assert_eq!(detect_content(&bytes).unwrap(), Format::Parquet);
}
#[test]
fn parquet_extension_detected() {
assert_eq!(
detect_extension(Path::new("x.parquet")),
Some(Format::Parquet)
);
assert_eq!(detect_extension(Path::new("x.pq")), Some(Format::Parquet));
}
#[test]
fn inspect_parquet() {
let dir = tempdir().unwrap();
let p = dir.path().join("x.parquet");
fs::write(
&p,
sheeter_engine::to_parquet(&hub(r#"[{"a":1,"b":"x"},{"a":2,"b":"y"}]"#)).unwrap(),
)
.unwrap();
let out = Command::cargo_bin("sheeter")
.unwrap()
.args(["inspect", p.to_str().unwrap()])
.output()
.unwrap();
assert!(
out.status.success(),
"inspect falhou: {}",
String::from_utf8_lossy(&out.stderr)
);
let stdout = String::from_utf8_lossy(&out.stdout);
assert!(stdout.contains("parquet"), "format: {stdout}");
assert!(stdout.contains("rows: 2"), "contagem: {stdout}");
assert!(stdout.contains("integer"), "tipo int: {stdout}");
assert!(stdout.contains("string"), "tipo str: {stdout}");
}
#[test]
fn query_reads_parquet() {
let dir = tempdir().unwrap();
let p = dir.path().join("data.parquet");
fs::write(
&p,
sheeter_engine::to_parquet(&hub(r#"[{"id":1,"name":"Alice"},{"id":2,"name":"Bob"}]"#))
.unwrap(),
)
.unwrap();
let out = Command::cargo_bin("sheeter")
.unwrap()
.current_dir(dir.path())
.args(["query", "SELECT name FROM data.parquet WHERE id = 2"])
.output()
.unwrap();
assert!(
out.status.success(),
"query falhou: {}",
String::from_utf8_lossy(&out.stderr)
);
let stdout = String::from_utf8_lossy(&out.stdout);
assert_eq!(stdout.trim(), r#"[{"name":"Bob"}]"#);
}
#[test]
fn view_reads_parquet() {
let dir = tempdir().unwrap();
let p = dir.path().join("x.parquet");
fs::write(
&p,
sheeter_engine::to_parquet(&hub(r#"[{"a":"hello"}]"#)).unwrap(),
)
.unwrap();
let out = Command::cargo_bin("sheeter")
.unwrap()
.args(["view", p.to_str().unwrap()])
.output()
.unwrap();
assert!(
out.status.success(),
"view falhou: {}",
String::from_utf8_lossy(&out.stderr)
);
let stdout = String::from_utf8_lossy(&out.stdout);
assert!(stdout.contains("hello"), "conteudo: {stdout}");
assert!(stdout.contains("Output table No. 1"), "cabecalho: {stdout}");
}
#[test]
fn xlsx_concat_accepts_parquet() {
let dir = tempdir().unwrap();
let p = dir.path().join("a.parquet");
fs::write(
&p,
sheeter_engine::to_parquet(&hub(r#"[{"v":1},{"v":2}]"#)).unwrap(),
)
.unwrap();
let out = dir.path().join("out.xlsx");
let status = std::process::Command::new(env!("CARGO_BIN_EXE_sheeter"))
.current_dir(dir.path())
.args(["xlsx:concat", "a.parquet", "-o", out.to_str().unwrap()])
.status()
.unwrap();
assert!(status.success(), "xlsx:concat falhou");
assert!(out.exists());
let sheets = CanonicalValue::from_xlsx_workbook(&fs::read(&out).unwrap()).unwrap();
assert_eq!(sheets.len(), 1);
assert_eq!(sheets[0].0, "data");
}