Files

151 lines
4.4 KiB
Rust

//! Contract tests for shared CLI helpers.
use common::{
CliError, ColorChoice, CommonArgs, ExitCode, InputFormat, RenderMode, emit_json,
emit_structured, formats, map_result_count, parse_color_choice, parse_format_choice,
parse_input_format, should_read_stdin,
};
use serde::Serialize;
use serde_json::{Map, Value, json};
#[test]
fn parses_shared_choice_values() {
let parsed = CommonArgs {
json: true,
format: None,
input_format: parse_input_format("jsonl").expect("input format"),
color: parse_color_choice("never").expect("color choice"),
quiet: false,
};
assert!(parsed.json);
assert_eq!(parsed.input_format, InputFormat::Jsonl);
assert_eq!(parsed.color, ColorChoice::Never);
assert_eq!(parsed.render_mode(), RenderMode::Json);
assert_eq!(
parse_format_choice("toon").expect("format choice"),
RenderMode::Toon
);
let input_error = parse_input_format("yaml").expect_err("invalid input format");
let color_error = parse_color_choice("always").expect_err("invalid color choice");
let format_error = parse_format_choice("yaml").expect_err("invalid format choice");
assert!(matches!(input_error, CliError::Usage(_)));
assert!(matches!(color_error, CliError::Usage(_)));
assert!(matches!(format_error, CliError::Usage(_)));
}
#[test]
fn auto_stdin_reads_only_without_explicit_input_and_when_not_terminal() {
assert!(should_read_stdin(false, false));
assert!(!should_read_stdin(false, true));
assert!(!should_read_stdin(true, false));
}
#[test]
fn maps_result_count_to_exit_code() {
assert_eq!(map_result_count(1), ExitCode::Success);
assert_eq!(map_result_count(0), ExitCode::NoResults);
}
#[test]
fn emits_single_json_document() {
#[derive(Serialize)]
struct Demo<'a> {
name: &'a str,
}
let rendered = emit_json(&Demo { name: "jsonlgrep" }).expect("json output");
assert_eq!(rendered, "{\"name\":\"jsonlgrep\"}\n");
}
#[test]
fn emits_structured_toon_document() {
#[derive(Serialize)]
struct Demo<'a> {
name: &'a str,
count: u8,
}
let rendered = emit_structured(
&Demo {
name: "jsonlgrep",
count: 2,
},
RenderMode::Toon,
)
.expect("toon output");
let lines = rendered.lines().collect::<Vec<_>>();
assert_eq!(lines.len(), 2);
assert!(lines.contains(&"name: jsonlgrep"));
assert!(lines.contains(&"count: 2"));
assert!(rendered.ends_with('\n'));
}
#[test]
fn structured_toon_render_rejects_values_past_explicit_depth_limit() {
let error = emit_structured(&deeply_nested_value(260), RenderMode::Toon)
.expect_err("deep TOON encode should fail");
assert!(matches!(
error,
CliError::Runtime(message) if message.contains("maximum TOON encoding depth")
));
}
#[test]
fn exposes_json_family_format_modules() {
let records = vec![json!({"id": "user-1", "active": true})];
let ison = formats::ison::encode_records(&records, "record").expect("ison records");
assert!(ison.contains("object.record|"));
assert!(ison.contains("id:str"));
assert!(ison.contains("active:bool"));
assert_eq!(
formats::ison::decode_record_line(&ison, 1).expect("ison decode"),
records[0]
);
let zon = formats::zon::encode_value(&records[0]).expect("zon");
assert!(zon.contains("id:\"user-1\""));
assert_eq!(
formats::zon::decode_str(&zon).expect("zon decode"),
records[0]
);
let tonl = formats::tonl::encode_documents(&records).expect("tonl");
assert!(tonl.contains("id = \"user-1\""));
assert_eq!(
formats::tonl::decode_documents(&tonl).expect("tonl decode"),
records
);
}
fn deeply_nested_value(depth: usize) -> Value {
let mut value = json!(1);
for index in 0..depth {
let mut object = Map::new();
object.insert(format!("k{index}"), value);
value = Value::Object(object);
}
value
}
#[test]
fn isonl_roundtrips_pipe_inside_quoted_string() {
let records = vec![json!({"message": "left|right", "ok": true})];
let ison = formats::ison::encode_records(&records, "record").expect("ison records");
assert_eq!(
formats::ison::decode_record_line(ison.trim_end(), 1).expect("ison decode"),
records[0]
);
assert_eq!(
formats::ison::decode_records(&ison).expect("ison batch decode"),
records
);
}