mod common;
use common::*;
#[test]
fn test_brief_output_mode() {
let input = r#"{"level": "INFO", "message": "test message", "user": "alice"}
{"level": "ERROR", "message": "error occurred", "user": "bob"}"#;
let (stdout, _stderr, exit_code) = run_kelora_with_input(&["-f", "json", "--brief"], input);
assert_eq!(
exit_code, 0,
"kelora should exit successfully with brief mode"
);
let lines: Vec<&str> = stdout.trim().split('\n').collect();
assert_eq!(lines.len(), 2, "Should output 2 lines");
assert_eq!(lines[0], "INFO test message alice");
assert_eq!(lines[1], "ERROR error occurred bob");
assert!(
!stdout.contains("level="),
"Brief mode should not contain keys"
);
assert!(
!stdout.contains("message="),
"Brief mode should not contain keys"
);
assert!(
!stdout.contains("user="),
"Brief mode should not contain keys"
);
}
#[test]
fn test_brief_output_mode_short_form() {
let input = r#"{"level": "INFO", "message": "hello world"}"#;
let (stdout, _stderr, exit_code) = run_kelora_with_input(&["-f", "json", "-b"], input);
assert_eq!(
exit_code, 0,
"kelora should exit successfully with -b short form"
);
assert_eq!(stdout.trim(), "INFO hello world");
assert!(
!stdout.contains("level="),
"Brief mode should not contain keys"
);
}
#[test]
fn test_core_field_filtering() {
let input = r#"{"ts": "2024-01-01T12:00:00Z", "level": "ERROR", "message": "Test message", "user": "alice", "status": 500}"#;
let (stdout, _, exit_code) = run_kelora_with_input(&["-f", "json", "--core"], input);
assert_eq!(exit_code, 0, "kelora should exit successfully with --core");
assert!(stdout.contains("ts="), "Should contain ts field");
assert!(stdout.contains("level="), "Should contain level field");
assert!(stdout.contains("message="), "Should contain message field");
assert!(
!stdout.contains("user="),
"Should not contain non-core user field"
);
assert!(
!stdout.contains("status="),
"Should not contain non-core status field"
);
}
#[test]
fn test_core_field_filtering_short_flag() {
let input = r#"{"ts": "2024-01-01T12:00:00Z", "level": "ERROR", "message": "Test message", "user": "alice"}"#;
let (stdout, _stderr, exit_code) = run_kelora_with_input(&["-f", "json", "-c"], input);
assert_eq!(
exit_code, 0,
"kelora should exit successfully with -c short flag"
);
assert!(stdout.contains("ts="), "Should contain ts field");
assert!(stdout.contains("level="), "Should contain level field");
assert!(stdout.contains("message="), "Should contain message field");
assert!(
!stdout.contains("user="),
"Should not contain non-core user field"
);
}
#[test]
fn test_core_field_with_alternative_names() {
let input = r#"{"ts": "2024-01-01T12:00:00Z", "lvl": "WARN", "msg": "Alternative names", "extra_data": "ignored"}"#;
let (stdout, _stderr, exit_code) = run_kelora_with_input(&["-f", "json", "--core"], input);
assert_eq!(
exit_code, 0,
"kelora should exit successfully with alternative core field names"
);
assert!(stdout.contains("ts="), "Should contain ts field");
assert!(stdout.contains("lvl="), "Should contain lvl field");
assert!(stdout.contains("msg="), "Should contain msg field");
assert!(
!stdout.contains("extra_data="),
"Should not contain non-core field"
);
}
#[test]
fn test_core_field_plus_additional_keys() {
let input = r#"{"ts": "2024-01-01T12:00:00Z", "level": "ERROR", "message": "Test message", "user": "alice", "status": 500}"#;
let (stdout, _stderr, exit_code) =
run_kelora_with_input(&["-f", "json", "--core", "--keys", "user,status"], input);
assert_eq!(
exit_code, 0,
"kelora should exit successfully with --core and --keys"
);
assert!(stdout.contains("ts="), "Should contain ts field");
assert!(stdout.contains("level="), "Should contain level field");
assert!(stdout.contains("message="), "Should contain message field");
assert!(
stdout.contains("user="),
"Should contain user field from --keys"
);
assert!(
stdout.contains("status="),
"Should contain status field from --keys"
);
}
#[test]
fn test_core_field_with_syslog() {
let input = r#"<34>Jan 1 12:00:00 myhost myapp[1234]: Test syslog message"#;
let (stdout, _stderr, exit_code) = run_kelora_with_input(&["-f", "syslog", "--core"], input);
assert_eq!(
exit_code, 0,
"kelora should exit successfully with syslog and --core"
);
assert!(
stdout.contains("severity="),
"Should contain severity field"
);
assert!(stdout.contains("ts="), "Should contain ts field");
assert!(stdout.contains("msg="), "Should contain msg field");
assert!(
!stdout.contains("facility="),
"Should not contain facility field"
);
assert!(!stdout.contains("host="), "Should not contain host field");
assert!(!stdout.contains("prog="), "Should not contain prog field");
}
#[test]
fn test_core_field_with_exec_created_fields() {
let input = r#"{"original_time": "2024-01-01T12:00:00Z", "orig_level": "ERROR", "orig_msg": "Test message", "user": "alice"}"#;
let (stdout, _stderr, exit_code) = run_kelora_with_input(
&[
"-f",
"json",
"--exec",
"e.timestamp = e.original_time; e.level = e.orig_level; e.message = e.orig_msg",
"--core",
"--keys",
"user",
],
input,
);
assert_eq!(
exit_code, 0,
"kelora should exit successfully with exec-created core fields"
);
assert!(
stdout.contains("user="),
"Should contain user field from --keys"
);
assert!(
stdout.contains("timestamp="),
"Should contain exec-created timestamp field"
);
assert!(
stdout.contains("level="),
"Should contain exec-created level field"
);
assert!(
stdout.contains("message="),
"Should contain exec-created message field"
);
assert!(
!stdout.contains("original_time="),
"Should not contain original_time field"
);
assert!(
!stdout.contains("orig_level="),
"Should not contain orig_level field"
);
assert!(
!stdout.contains("orig_msg="),
"Should not contain orig_msg field"
);
}
#[test]
fn test_core_field_with_logfmt() {
let input =
r#"time=2024-01-01T12:00:00Z lvl=error msg="Test logfmt message" user=bob status=404"#;
let (stdout, _stderr, exit_code) = run_kelora_with_input(&["-f", "logfmt", "--core"], input);
assert_eq!(
exit_code, 0,
"kelora should exit successfully with logfmt and --core"
);
assert!(stdout.contains("time="), "Should contain time field");
assert!(stdout.contains("lvl="), "Should contain lvl field");
assert!(stdout.contains("msg="), "Should contain msg field");
assert!(!stdout.contains("user="), "Should not contain user field");
assert!(
!stdout.contains("status="),
"Should not contain status field"
);
}
#[test]
fn test_core_field_multiple_timestamp_variants() {
let input = r#"{"ts": "2024-01-01T12:00:00Z", "timestamp": "2024-01-01T13:00:00Z", "time": "2024-01-01T14:00:00Z", "level": "INFO", "message": "Multiple timestamps", "other": "data"}"#;
let (stdout, _stderr, exit_code) = run_kelora_with_input(&["-f", "json", "--core"], input);
assert_eq!(
exit_code, 0,
"kelora should exit successfully with multiple timestamp variants"
);
assert!(stdout.contains("ts="), "Should contain ts field");
assert!(stdout.contains("ts="), "Should contain ts field");
assert!(stdout.contains("time="), "Should contain time field");
assert!(stdout.contains("level="), "Should contain level field");
assert!(stdout.contains("message="), "Should contain message field");
assert!(
!stdout.contains("other="),
"Should not contain non-core other field"
);
}
#[test]
fn test_quiet_level_0_normal_output() {
let input = r#"{"level": "info", "message": "test"}"#;
let (stdout, stderr, exit_code) = run_kelora_with_input(
&[
"-f",
"json",
"--with-stats",
"--exec",
"print(\"Script output\")",
],
input,
);
assert_eq!(exit_code, 0);
assert!(stdout.contains("level='info'"));
assert!(stdout.contains("message='test'"));
assert!(stdout.contains("Script output"));
assert!(stderr.contains("Stats"));
assert!(stderr.contains("Lines processed"));
}
#[test]
fn test_quiet_level_1_suppress_diagnostics() {
let input = r#"{"level": "info", "message": "test"}"#;
let (stdout, stderr, exit_code) = run_kelora_with_input(
&[
"-f",
"json",
"--with-stats",
"--exec",
"print(\"Script output\")",
"-q",
],
input,
);
assert_eq!(exit_code, 0);
assert!(!stdout.contains("level='info'"));
assert!(!stdout.contains("message='test'"));
assert!(stdout.contains("Script output"));
assert!(stderr.contains("Stats"));
assert!(stderr.contains("Lines processed"));
}
#[test]
fn test_quiet_level_2_suppress_events() {
let input = r#"{"level": "info", "message": "test"}"#;
let (stdout, stderr, exit_code) = run_kelora_with_input(
&[
"-f",
"json",
"--with-stats",
"--exec",
"print(\"Script output\")",
"-q",
"--no-diagnostics",
],
input,
);
assert_eq!(exit_code, 0);
assert!(!stdout.contains("level='info'"));
assert!(!stdout.contains("message='test'"));
assert!(stdout.contains("Script output"));
assert!(stderr.contains("Stats"));
assert!(stderr.contains("Lines processed"));
}
#[test]
fn test_quiet_level_3_suppress_all() {
let input = r#"{"level": "info", "message": "test"}"#;
let (stdout, stderr, exit_code) = run_kelora_with_input(
&[
"-f",
"json",
"--with-stats",
"--exec",
"print(\"Script output\")",
"--silent",
],
input,
);
assert_eq!(exit_code, 0);
assert!(!stdout.contains("level='info'"));
assert!(!stdout.contains("message='test'"));
assert!(stdout.contains("Script output"));
assert!(!stderr.contains("Stats"));
assert!(!stderr.contains("Lines processed"));
assert_eq!(stderr.trim(), "");
}
#[test]
fn test_normalize_ts_normalizes_primary_timestamp_default_output() {
let input = r#"{"ts": "2025-01-15 10:00:00", "level": "INFO", "message": "Test"}"#;
let (stdout, _stderr, exit_code) = run_kelora_with_input(
&["-f", "json", "--input-tz", "UTC", "--normalize-ts"],
input,
);
assert_eq!(
exit_code, 0,
"kelora should exit successfully with --normalize-ts"
);
assert!(
stdout.contains("ts='2025-01-15T10:00:00+00:00'"),
"Primary timestamp should be normalized in default formatter output. stdout: {}",
stdout
);
assert!(
!stdout.contains("2025-01-15 10:00:00"),
"Original timestamp representation should be replaced in default output. stdout: {}",
stdout
);
}
#[test]
fn test_normalize_ts_normalizes_primary_timestamp_json_output() {
let input = r#"{"ts": "2025-01-15 10:00:00", "level": "INFO"}"#;
let (stdout, _stderr, exit_code) = run_kelora_with_input(
&[
"-f",
"json",
"--input-tz",
"UTC",
"--normalize-ts",
"-F",
"json",
],
input,
);
assert_eq!(
exit_code, 0,
"kelora should exit successfully with --normalize-ts"
);
assert!(
stdout.contains("\"ts\":\"2025-01-15T10:00:00+00:00\""),
"JSON formatter should receive normalized timestamp. stdout: {}",
stdout
);
assert!(
!stdout.contains("2025-01-15 10:00:00"),
"Original timestamp representation should be replaced in JSON output. stdout: {}",
stdout
);
}
#[test]
fn test_no_emoji_flag() {
let input = r#"{"level": "error", "message": "test error"}"#;
let (_stdout_with_emoji, _stderr_with_emoji, _) =
run_kelora_with_input(&["-f", "json", "--with-stats"], input);
let (stdout_no_emoji, stderr_no_emoji, _) =
run_kelora_with_input(&["-f", "json", "--with-stats", "--no-emoji"], input);
let has_emoji_chars = |s: &str| s.chars().any(|c| c as u32 > 0x1F000);
assert!(
!has_emoji_chars(&stdout_no_emoji) && !has_emoji_chars(&stderr_no_emoji),
"Output with --no-emoji should not contain emoji characters"
);
}
#[test]
fn test_force_color_flag() {
let input = r#"{"level": "error", "message": "test"}"#;
let (stdout, _stderr, exit_code) =
run_kelora_with_input(&["-f", "json", "--force-color"], input);
assert_eq!(exit_code, 0, "kelora should exit successfully");
assert!(!stdout.is_empty(), "Should produce output");
}
#[test]
fn test_no_color_flag() {
let input = r#"{"level": "error", "message": "test"}"#;
let (stdout, _stderr, exit_code) = run_kelora_with_input(&["-f", "json", "--no-color"], input);
assert_eq!(exit_code, 0, "kelora should exit successfully");
assert!(
!stdout.contains("\x1b["),
"Output with --no-color should not contain ANSI escape codes"
);
}
#[test]
fn test_wrap_and_no_wrap_flags() {
let long_message = "a".repeat(200);
let input = format!(r#"{{"level": "info", "message": "{}"}}"#, long_message);
let (stdout_default, _stderr, exit_code_default) =
run_kelora_with_input(&["-f", "json"], &input);
assert_eq!(exit_code_default, 0);
let (stdout_no_wrap, _stderr, exit_code_no_wrap) =
run_kelora_with_input(&["-f", "json", "--no-wrap"], &input);
assert_eq!(exit_code_no_wrap, 0);
let (stdout_wrap, _stderr, exit_code_wrap) =
run_kelora_with_input(&["-f", "json", "--wrap"], &input);
assert_eq!(exit_code_wrap, 0);
assert!(!stdout_default.is_empty());
assert!(!stdout_no_wrap.is_empty());
assert!(!stdout_wrap.is_empty());
let no_wrap_line_count = stdout_no_wrap.trim().lines().count();
assert_eq!(
no_wrap_line_count, 1,
"No-wrap output should be a single line"
);
}
#[test]
fn test_expand_nested_flag() {
let input = r#"{"level": "info", "metadata": {"user": "alice", "request_id": "123"}}"#;
let (stdout_default, _stderr, exit_code_default) =
run_kelora_with_input(&["-f", "json"], input);
assert_eq!(exit_code_default, 0);
let (stdout_expanded, _stderr, exit_code_expanded) =
run_kelora_with_input(&["-f", "json", "--expand-nested"], input);
assert_eq!(exit_code_expanded, 0);
assert!(!stdout_default.is_empty());
assert!(!stdout_expanded.is_empty());
assert!(
stdout_expanded.contains("metadata"),
"Expanded output should contain metadata field"
);
}
#[test]
fn test_mark_gaps_flag() {
let input = r#"{"ts": "2024-01-01T10:00:00Z", "message": "first"}
{"ts": "2024-01-01T10:00:01Z", "message": "second"}
{"ts": "2024-01-01T10:05:00Z", "message": "third with gap"}
{"ts": "2024-01-01T10:05:01Z", "message": "fourth"}"#;
let (stdout_default, _stderr, exit_code_default) =
run_kelora_with_input(&["-f", "json"], input);
assert_eq!(exit_code_default, 0);
let (stdout_gaps, stderr_gaps, exit_code_gaps) =
run_kelora_with_input(&["-f", "json", "--mark-gaps", "30s"], input);
assert_eq!(
exit_code_gaps, 0,
"kelora should exit successfully with --mark-gaps, stderr: {}",
stderr_gaps
);
assert!(!stdout_default.is_empty());
assert!(!stdout_gaps.is_empty());
assert!(stdout_gaps.contains("first"));
assert!(stdout_gaps.contains("second"));
assert!(stdout_gaps.contains("third with gap"));
assert!(stdout_gaps.contains("fourth"));
}
#[test]
fn test_conflicting_color_flags() {
let input = r#"{"level": "info", "message": "test"}"#;
let (_stdout, _stderr, exit_code) =
run_kelora_with_input(&["-f", "json", "--force-color", "--no-color"], input);
assert!(
exit_code == 0 || exit_code == 2,
"Should either succeed or fail with usage error"
);
}
#[test]
fn test_csv_without_keys_suggests_column_order_example() {
let input = r#"{"ts": "2024-01-01T10:00:00Z", "level": "info", "msg": "hello"}"#;
let (_stdout, stderr, exit_code) = run_kelora_with_input(&["-f", "json", "-F", "csv"], input);
assert_ne!(exit_code, 0, "csv without keys should fail");
assert!(
stderr.contains("CSV output requires --keys to define column order"),
"stderr should explain the missing keys requirement: {}",
stderr
);
assert!(
stderr.contains("--keys ts,level,msg") && stderr.contains("Use -s"),
"stderr should include an example and field discovery hint: {}",
stderr
);
}
#[test]
fn test_keymap_without_single_key_suggests_example() {
let input = r#"{"level": "info", "msg": "hello"}"#;
let (_stdout, stderr, exit_code) =
run_kelora_with_input(&["-f", "json", "-F", "keymap"], input);
assert_ne!(exit_code, 0, "keymap without a single key should fail");
assert!(
stderr.contains("keymap output requires exactly one field via --keys"),
"stderr should explain the single-field requirement: {}",
stderr
);
assert!(
stderr.contains("--keys level"),
"stderr should include a concrete example: {}",
stderr
);
}
#[test]
fn test_tailmap_without_single_key_suggests_numeric_example() {
let input = r#"{"latency_ms": 123, "msg": "hello"}"#;
let (_stdout, stderr, exit_code) =
run_kelora_with_input(&["-f", "json", "-F", "tailmap"], input);
assert_ne!(exit_code, 0, "tailmap without a single key should fail");
assert!(
stderr.contains("tailmap output requires exactly one numeric field via --keys"),
"stderr should explain the single numeric field requirement: {}",
stderr
);
assert!(
stderr.contains("--keys latency_ms"),
"stderr should include a numeric-field example: {}",
stderr
);
}
#[test]
fn test_drain_without_keys_suggests_single_field_example() {
let input = r#"{"msg": "hello world"}"#;
let (_stdout, stderr, exit_code) = run_kelora_with_input(&["-f", "json", "--drain"], input);
assert_ne!(exit_code, 0, "drain without keys should fail");
assert!(
stderr.contains("--drain requires exactly one effective field in --keys"),
"stderr should explain the drain field requirement: {}",
stderr
);
assert!(
stderr.contains("--keys msg") && stderr.contains("Use -s"),
"stderr should include an example and discovery hint: {}",
stderr
);
}
#[test]
fn test_drain_parallel_suggests_sequential_rerun() {
let input = r#"{"msg": "hello world"}"#;
let (_stdout, stderr, exit_code) = run_kelora_with_input(
&["-f", "json", "--parallel", "--drain", "--keys", "msg"],
input,
);
assert_ne!(exit_code, 0, "drain with parallel mode should fail");
assert!(
stderr.contains("--drain summary is not supported with --parallel"),
"stderr should explain the mode conflict: {}",
stderr
);
assert!(
stderr.contains("Rerun without --parallel"),
"stderr should suggest the sequential rerun: {}",
stderr
);
}
#[test]
fn test_wrap_with_different_formatters() {
let long_message = "x".repeat(150);
let input = format!(r#"{{"message": "{}"}}"#, long_message);
let (stdout_json, _stderr, exit_code_json) =
run_kelora_with_input(&["-f", "json", "-F", "json", "--no-wrap"], &input);
assert_eq!(exit_code_json, 0);
assert_eq!(stdout_json.trim().lines().count(), 1);
let (stdout_default, _stderr, exit_code_default) =
run_kelora_with_input(&["-f", "json", "--no-wrap"], &input);
assert_eq!(exit_code_default, 0);
assert!(!stdout_default.is_empty());
}
#[test]
fn test_multiple_display_flags_combination() {
let input = r#"{"level": "error", "message": "test error", "nested": {"key": "value"}}"#;
let (stdout, _stderr, exit_code) = run_kelora_with_input(
&[
"-f",
"json",
"--no-emoji",
"--no-color",
"--no-wrap",
"--expand-nested",
],
input,
);
assert_eq!(exit_code, 0, "kelora should handle multiple display flags");
assert!(!stdout.is_empty(), "Should produce output");
}
#[test]
fn test_color_with_filtering() {
let input = r#"{"level": "info", "message": "info msg"}
{"level": "error", "message": "error msg"}"#;
let (stdout, _stderr, exit_code) = run_kelora_with_input(
&[
"-f",
"json",
"--filter",
"e.level == \"error\"",
"--no-color",
],
input,
);
assert_eq!(exit_code, 0);
assert!(stdout.contains("error msg"));
assert!(!stdout.contains("info msg"));
assert!(!stdout.contains("\x1b["));
}