//! Integration tests for the `tonl` command. use std::fs; use std::path::PathBuf; use assert_cmd::Command; use predicates::prelude::*; use serde_json::Value; use tempfile::tempdir; fn fixture(path: &str) -> PathBuf { let fixture = PathBuf::from(env!("CARGO_MANIFEST_DIR")) .join("..") .join("..") .join("fixtures") .join("json-family") .join("tonl") .join(path); assert!( fixture.exists(), "missing fixture `{path}` at {}", fixture.display() ); fixture } fn cargo_tonl() -> Command { Command::cargo_bin("tonl").expect("binary") } fn fixture_text(path: &str) -> String { fs::read_to_string(fixture(path)).expect("fixture text") } #[test] fn roundtrip_json_object_through_tonl() { let encoded = cargo_tonl() .args(["encode"]) .arg(fixture("sample.json")) .assert() .success() .stdout(predicate::str::contains("name = \"Ada\"")) .get_output() .stdout .clone(); let decoded = cargo_tonl() .args(["decode"]) .write_stdin(encoded) .assert() .success() .get_output() .stdout .clone(); let expected: Value = serde_json::from_str(&fixture_text("sample.json")).expect("expected json"); let actual: Value = serde_json::from_slice(&decoded).expect("decoded json"); assert_eq!(actual, expected); } #[test] fn invalid_input_reports_line_diagnostics() { cargo_tonl() .args(["decode"]) .arg(fixture("invalid.tonl")) .assert() .failure() .code(2) .stderr(predicate::str::contains("line 3")) .stderr(predicate::str::contains("valid JSON value")); } #[test] fn supports_path_and_stdin_input() { cargo_tonl() .args(["decode"]) .arg(fixture("sample.tonl")) .assert() .success() .stdout(predicate::str::contains("\"name\":\"Ada\"")); cargo_tonl() .args(["encode"]) .write_stdin(fixture_text("sample.json")) .assert() .success() .stdout(predicate::str::contains("score = 42")); } #[test] fn encode_json_wrapper_reports_tonl_text() { cargo_tonl() .args(["encode", "--json"]) .arg(fixture("sample.json")) .assert() .success() .stdout(predicate::str::contains("\"format\":\"tonl\"")) .stdout(predicate::str::contains("\"records\":1")) .stdout(predicate::str::contains("name = \\\"Ada\\\"")); } #[test] fn encode_toon_wrapper_reports_tonl_text() { cargo_tonl() .args(["encode", "--format", "toon"]) .arg(fixture("sample.json")) .assert() .success() .stdout(predicate::str::contains("format: tonl")) .stdout(predicate::str::contains("records: 1")) .stdout(predicate::str::contains("name = \\\"Ada\\\"")); } #[test] fn query_filters_records_by_field_value() { let output = cargo_tonl() .args(["query", "--where", "active=true"]) .arg(fixture("records.tonl")) .assert() .success() .get_output() .stdout .clone(); let record: Value = serde_json::from_slice(&output).unwrap_or_else(|error| { panic!( "query output should be a JSON record: {error}\n{}", String::from_utf8_lossy(&output) ) }); assert_eq!( record["id"], "user-1", "stdout={}", String::from_utf8_lossy(&output) ); assert_eq!( record["active"], true, "stdout={}", String::from_utf8_lossy(&output) ); } #[test] fn validate_reports_failures() { cargo_tonl() .args(["validate"]) .arg(fixture("invalid.tonl")) .assert() .failure() .code(2) .stderr(predicate::str::contains("invalid TONL")); } #[test] fn index_build_and_query_lookup_record() { let temp = tempdir().expect("tempdir"); let index = temp.path().join("records.tonl-index.json"); cargo_tonl() .args(["index", "build", "--key", "id", "--output"]) .arg(&index) .arg(fixture("records.tonl")) .assert() .success() .stdout(predicate::str::contains("indexed 2 records")); cargo_tonl() .args(["index", "query"]) .arg(&index) .arg("user-2") .assert() .success() .stdout(predicate::str::contains("\"name\":\"Bob\"")) .stdout(predicate::str::contains("\"active\":false")); } #[test] fn stream_query_smoke_filters_jsonl_records() { cargo_tonl() .args(["stream", "query", "--where", "active=false"]) .arg(fixture("records.jsonl")) .assert() .success() .stdout(predicate::str::contains("\"id\":\"user-2\"")) .stdout(predicate::str::contains("\"id\":\"user-1\"").not()); } #[test] fn etl_converts_jsonl_to_tonl_and_tonl_to_jsonl() { cargo_tonl() .args(["etl", "--from", "jsonl", "--to", "tonl"]) .arg(fixture("records.jsonl")) .assert() .success() .stdout(predicate::str::contains("---")) .stdout(predicate::str::contains("id = \"user-1\"")); cargo_tonl() .args(["etl", "--from", "tonl", "--to", "jsonl"]) .arg(fixture("records.tonl")) .assert() .success() .stdout(predicate::str::contains("{\"id\":\"user-1\"")) .stdout(predicate::str::contains("{\"id\":\"user-2\"")); } #[test] fn help_lists_full_tool_surface() { cargo_tonl() .arg("--help") .assert() .success() .stdout(predicate::str::contains("encode")) .stdout(predicate::str::contains("decode")) .stdout(predicate::str::contains("query")) .stdout(predicate::str::contains("validate")) .stdout(predicate::str::contains("index build")) .stdout(predicate::str::contains("index query")) .stdout(predicate::str::contains("stream query")) .stdout(predicate::str::contains("etl")); }