use assert_cmd::Command;
use predicates::prelude::*;
fn jpx() -> Command {
assert_cmd::cargo_bin_cmd!("jpx")
}
mod help_and_version {
use super::*;
#[test]
fn version_shows_jpx_pattern() {
jpx()
.arg("--version")
.assert()
.success()
.stdout(predicate::str::is_match(r"jpx \d+\.\d+\.\d+").unwrap());
}
#[test]
fn short_help_exits_success() {
jpx()
.arg("-h")
.assert()
.success()
.stdout(predicate::str::contains("Usage:"));
}
#[test]
fn long_help_shows_examples() {
jpx()
.arg("--help")
.assert()
.success()
.stdout(predicate::str::contains("EXAMPLES:"));
}
}
mod expression_input {
use super::*;
#[test]
fn positional_expression_from_stdin() {
jpx()
.arg("name")
.write_stdin(r#"{"name":"alice"}"#)
.assert()
.success()
.stdout(predicate::str::contains("\"alice\""));
}
#[test]
fn flag_expression_from_stdin() {
jpx()
.args(["-e", "name"])
.write_stdin(r#"{"name":"alice"}"#)
.assert()
.success()
.stdout(predicate::str::contains("\"alice\""));
}
#[test]
fn multiple_flag_expressions_chain() {
jpx()
.args(["-e", "[*].name", "-e", "sort(@)"])
.write_stdin(r#"[{"name":"b"},{"name":"a"}]"#)
.assert()
.success()
.stdout(predicate::str::contains("\"a\"").and(predicate::str::contains("\"b\"")));
}
#[test]
fn multiple_positional_expressions_chain() {
jpx()
.args(["[*].name", "sort(@)"])
.write_stdin(r#"[{"name":"b"},{"name":"a"}]"#)
.assert()
.success()
.stdout(predicate::str::contains("\"a\"").and(predicate::str::contains("\"b\"")));
}
#[test]
fn missing_expression_defaults_to_identity() {
jpx().write_stdin("{}").assert().success().stdout("{}\n");
}
#[test]
fn flag_and_positional_conflict() {
jpx()
.args(["-e", "x", "y"])
.assert()
.failure()
.code(2)
.stderr(predicate::str::contains("cannot be used with"));
}
}
mod mode_flags {
use super::*;
#[test]
fn strict_blocks_extension_functions() {
jpx()
.args(["--strict", "-n", "upper('hello')"])
.assert()
.failure()
.code(1)
.stderr(
predicate::str::contains("Unknown function")
.or(predicate::str::contains("undefined function")),
);
}
#[test]
fn strict_allows_standard_functions() {
jpx()
.args(["--strict", "length(@)"])
.write_stdin("[1,2,3]")
.assert()
.success()
.stdout(predicate::str::contains("3"));
}
#[test]
fn quiet_suppresses_stream_parse_errors() {
jpx()
.args(["-q", "--stream", "a"])
.write_stdin("bad\n{\"a\":1}")
.assert()
.success()
.stdout(predicate::str::contains("1"))
.stderr(predicate::str::is_empty());
}
#[test]
fn verbose_shows_timing() {
jpx()
.args(["-v", "--color", "never", "@"])
.write_stdin("{}")
.assert()
.success()
.stderr(predicate::str::contains("Total time:"));
}
#[test]
fn verbose_shows_input_info() {
jpx()
.args(["-v", "--color", "never", "@"])
.write_stdin(r#"{"a":1}"#)
.assert()
.success()
.stderr(predicate::str::contains("Input:"));
}
#[test]
fn verbose_strict_shows_mode() {
jpx()
.args(["-v", "--strict", "--color", "never", "@"])
.write_stdin("{}")
.assert()
.success()
.stderr(predicate::str::contains("Mode: strict"));
}
#[test]
fn debug_shows_diagnostic_info() {
jpx()
.args(["--debug", "--color", "never", "-n", "@"])
.assert()
.success()
.stderr(
predicate::str::contains("jpx debug info")
.and(predicate::str::contains("Version"))
.and(predicate::str::contains("Environment"))
.and(predicate::str::contains("Effective settings")),
);
}
#[test]
fn exit_status_truthy_exits_zero() {
jpx()
.args(["-x", "@"])
.write_stdin(r#"{"enabled":true}"#)
.assert()
.success();
}
#[test]
fn exit_status_false_exits_one() {
jpx()
.args(["-x", "enabled"])
.write_stdin(r#"{"enabled":false}"#)
.assert()
.failure()
.code(1);
}
#[test]
fn exit_status_null_exits_one() {
jpx()
.args(["-x", "missing"])
.write_stdin(r#"{"a":1}"#)
.assert()
.failure()
.code(1);
}
#[test]
fn exit_status_string_exits_zero() {
jpx()
.args(["-x", "name"])
.write_stdin(r#"{"name":"alice"}"#)
.assert()
.success();
}
#[test]
fn exit_status_number_exits_zero() {
jpx()
.args(["-x", "count"])
.write_stdin(r#"{"count":0}"#)
.assert()
.success();
}
#[test]
fn exit_status_stream_truthy() {
jpx()
.args(["-x", "--stream", "a"])
.write_stdin("{\"a\":1}\n{\"a\":2}")
.assert()
.success();
}
#[test]
fn exit_status_stream_all_null() {
jpx()
.args(["-x", "--stream", "missing"])
.write_stdin("{\"a\":1}\n{\"a\":2}")
.assert()
.failure()
.code(1);
}
#[test]
fn without_exit_status_null_still_exits_zero() {
jpx()
.arg("missing")
.write_stdin(r#"{"a":1}"#)
.assert()
.success();
}
}
mod flag_conflicts {
use super::*;
#[test]
fn yaml_csv_conflict() {
jpx()
.args(["--yaml", "--csv", "@"])
.write_stdin("{}")
.assert()
.failure()
.code(2)
.stderr(predicate::str::contains("cannot be used with"));
}
#[test]
fn yaml_toml_conflict() {
jpx()
.args(["--yaml", "--toml", "@"])
.write_stdin("{}")
.assert()
.failure()
.code(2)
.stderr(predicate::str::contains("cannot be used with"));
}
#[test]
fn csv_tsv_conflict() {
jpx()
.args(["--csv", "--tsv", "@"])
.write_stdin("{}")
.assert()
.failure()
.code(2)
.stderr(predicate::str::contains("cannot be used with"));
}
#[test]
fn lines_table_conflict() {
jpx()
.args(["--lines", "--table", "@"])
.write_stdin("{}")
.assert()
.failure()
.code(2)
.stderr(predicate::str::contains("cannot be used with"));
}
#[test]
fn stream_slurp_conflict() {
jpx()
.args(["--stream", "--slurp", "@"])
.write_stdin("{}")
.assert()
.failure()
.code(2)
.stderr(predicate::str::contains("cannot be used with"));
}
#[test]
fn stream_null_input_conflict() {
jpx()
.args(["--stream", "--null-input", "@"])
.assert()
.failure()
.code(2)
.stderr(predicate::str::contains("cannot be used with"));
}
#[test]
fn table_style_requires_table() {
jpx()
.args(["--table-style", "ascii", "@"])
.write_stdin("{}")
.assert()
.failure()
.code(2)
.stderr(predicate::str::contains("required"));
}
#[test]
fn types_requires_paths() {
jpx()
.args(["--types", "@"])
.write_stdin("{}")
.assert()
.failure()
.code(2)
.stderr(predicate::str::contains("required"));
}
#[test]
fn values_requires_paths() {
jpx()
.args(["--values", "@"])
.write_stdin("{}")
.assert()
.failure()
.code(2)
.stderr(predicate::str::contains("required"));
}
#[test]
fn expression_flag_and_query_file_conflict() {
jpx()
.args(["-e", "x", "-Q", "nonexistent.txt"])
.assert()
.failure()
.code(2)
.stderr(predicate::str::contains("cannot be used with"));
}
}
mod shell_completions {
use super::*;
#[test]
fn bash_completions() {
jpx()
.args(["--completions", "bash"])
.assert()
.success()
.stdout(predicate::str::is_empty().not());
}
#[test]
fn zsh_completions() {
jpx()
.args(["--completions", "zsh"])
.assert()
.success()
.stdout(predicate::str::is_empty().not());
}
#[test]
fn fish_completions() {
jpx()
.args(["--completions", "fish"])
.assert()
.success()
.stdout(predicate::str::is_empty().not());
}
#[test]
fn powershell_completions() {
jpx()
.args(["--completions", "powershell"])
.assert()
.success()
.stdout(predicate::str::is_empty().not());
}
}