225 lines
5.9 KiB
Rust
225 lines
5.9 KiB
Rust
//! 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"));
|
|
}
|