//! 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::>(); 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 ); }