mod common;
use common::*;
#[test]
fn test_error_handling_resilient_mode() {
let input = r#"{"level": "INFO", "status": 200}
invalid json line
{"level": "ERROR", "status": 500}"#;
let (stdout, _stderr, exit_code) = run_kelora_with_input(&["-f", "json"], input);
assert_eq!(
exit_code, 1,
"kelora should exit with error code when errors occur, even in resilient mode"
);
let lines: Vec<&str> = stdout.trim().split('\n').collect();
assert_eq!(
lines.len(),
2,
"Should skip invalid line and output 2 valid lines"
);
}
#[test]
fn test_error_handling_resilient_with_summary() {
let input = r#"{"level": "INFO", "status": 200}
invalid json line
{"level": "ERROR", "status": 500}"#;
let (stdout, _stderr, exit_code) = run_kelora_with_input(&["-f", "json", "-F", "json"], input);
assert_eq!(
exit_code, 1,
"kelora should exit with error code when errors occur, even in resilient mode"
);
let lines: Vec<&str> = stdout
.trim()
.split('\n')
.filter(|l| !l.is_empty())
.collect();
assert_eq!(
lines.len(),
2,
"Should output 2 valid lines, skipping invalid line"
);
for line in &lines {
serde_json::from_str::<serde_json::Value>(line).unwrap_or_else(|_| {
panic!("All output lines should be valid JSON, but got: '{}'", line)
});
}
}
#[test]
fn test_error_handling_resilient_mixed_input() {
let input = r#"{"valid": "json", "status": 200}
{malformed json line}
{"another": "valid", "status": 404}
not json at all
{"final": "entry", "status": 500}"#;
let (stdout, _stderr, exit_code) = run_kelora_with_input(&["-f", "json", "-F", "json"], input);
assert_eq!(
exit_code, 1,
"kelora should exit with error code when errors occur, even in resilient mode"
);
let lines: Vec<&str> = stdout.trim().split('\n').collect();
assert_eq!(
lines.len(),
3,
"Should output 3 valid JSON lines, skipping malformed ones"
);
for line in lines {
serde_json::from_str::<serde_json::Value>(line)
.expect("All output lines should be valid JSON");
}
}
#[test]
fn test_error_handling_strict_mode() {
let input = r#"{"level": "INFO", "status": 200}
invalid json line
{"level": "ERROR", "status": 500}"#;
let (stdout, _stderr, exit_code) = run_kelora_with_input(&["-f", "json", "--strict"], input);
assert_ne!(
exit_code, 0,
"kelora should exit with error code in strict mode when encountering invalid input"
);
let lines: Vec<&str> = stdout
.trim()
.split('\n')
.filter(|l| !l.is_empty())
.collect();
assert!(
lines.len() <= 1,
"Should output at most one line before failing in strict mode"
);
}
#[test]
fn test_quiet_levels_with_errors() {
let input = r#"{"level": "info", "message": "test"}"#;
let (stdout, _stderr, exit_code) = run_kelora_with_input(
&[
"-f",
"json",
"--filter",
"e.nonexistent.field == true",
"--strict",
"--silent",
],
input,
);
assert_ne!(exit_code, 0);
assert_eq!(stdout.trim(), "");
}
#[test]
fn test_error_stats_sequential_mode() {
let input = r#"{"valid": "json", "status": 200}
{malformed json line}
{"another": "valid", "status": 404}
not json at all
{"final": "entry", "status": 500}"#;
let (stdout, stderr, exit_code) = run_kelora_with_input(&["-f", "json", "--with-stats"], input);
assert_eq!(
exit_code, 1,
"Sequential mode should return a non-zero exit status when parse errors occur"
);
let lines: Vec<&str> = stdout.trim().lines().collect();
assert_eq!(lines.len(), 3, "Should emit only the valid JSON lines");
let stats = extract_stats_lines(&stderr);
assert_eq!(
stats_line(&stats, "Lines processed:"),
"Lines processed: 5 total, 0 filtered (0.0%), 2 errors (40.0%)"
);
assert_eq!(
stats_line(&stats, "Events created:"),
"Events created: 3 total, 3 output, 0 filtered (0.0%)"
);
}
#[test]
fn test_error_stats_parallel_mode() {
let input = r#"{"valid": "json", "status": 200}
{malformed json line}
{"another": "valid", "status": 404}
not json at all
{"final": "entry", "status": 500}"#;
let (stdout, stderr, exit_code) = run_kelora_with_input(
&[
"-f",
"json",
"--with-stats",
"--parallel",
"--batch-size",
"2",
],
input,
);
assert_eq!(
exit_code, 1,
"Parallel mode should continue despite parse errors and report failure status"
);
let lines: Vec<&str> = stdout.trim().lines().collect();
assert_eq!(lines.len(), 3, "Should emit only the valid JSON lines");
let stats = extract_stats_lines(&stderr);
assert_eq!(
stats_line(&stats, "Lines processed:"),
"Lines processed: 5 total, 0 filtered (0.0%), 2 errors (40.0%)"
);
assert_eq!(
stats_line(&stats, "Events created:"),
"Events created: 3 total, 3 output, 0 filtered (0.0%)"
);
}
#[test]
fn test_error_stats_with_filter_expression() {
let input = r#"{"valid": "json", "status": 200}
{malformed json line}
{"another": "valid", "status": 404}
not json at all
{"final": "entry", "status": 500}"#;
let (stdout, stderr, exit_code) = run_kelora_with_input(
&["-f", "json", "--filter", "e.status >= 400", "--with-stats"],
input,
);
assert_eq!(
exit_code, 1,
"Filtering to valid events should still report errors in exit status"
);
let lines: Vec<&str> = stdout.trim().lines().collect();
assert_eq!(lines.len(), 2, "Should emit only events with status >= 400");
let stats = extract_stats_lines(&stderr);
assert_eq!(
stats_line(&stats, "Lines processed:"),
"Lines processed: 5 total, 0 filtered (0.0%), 2 errors (40.0%)"
);
assert_eq!(
stats_line(&stats, "Events created:"),
"Events created: 3 total, 2 output, 1 filtered (33.3%)"
);
}
#[test]
fn test_exec_type_errors_are_reported_in_default_summary() {
let input = r#"{"level": "INFO"}"#;
let (_stdout, stderr, exit_code) =
run_kelora_with_input(&["-f", "json", "--exec", "e.level / 5"], input);
assert_ne!(
exit_code, 0,
"runtime exec errors should affect the exit code"
);
assert!(
stderr.contains("Exec errors:") || stderr.contains("Mixed errors:"),
"stderr should include a runtime error summary: {}",
stderr
);
}
#[test]
fn test_error_stats_with_ignore_lines() {
let input = r#"# This is a comment
{"valid": "json", "status": 200}
{malformed json line}
# Another comment
{"another": "valid", "status": 404}"#;
let (stdout, stderr, exit_code) = run_kelora_with_input(
&["-f", "json", "--ignore-lines", "^#", "--with-stats"],
input,
);
assert_eq!(
exit_code, 1,
"Ignoring comments still propagates parse errors in sequential mode"
);
let lines: Vec<&str> = stdout.trim().lines().collect();
assert_eq!(lines.len(), 2, "Should emit the two valid JSON lines");
let stats = extract_stats_lines(&stderr);
assert_eq!(
stats_line(&stats, "Lines processed:"),
"Lines processed: 5 total, 2 filtered (40.0%), 1 errors (20.0%)"
);
assert_eq!(
stats_line(&stats, "Events created:"),
"Events created: 2 total, 2 output, 0 filtered (0.0%)"
);
}
#[test]
fn test_error_stats_no_errors() {
let input = r#"{"valid": "json", "status": 200}
{"another": "valid", "status": 404}
{"final": "entry", "status": 500}"#;
let (stdout, stderr, exit_code) = run_kelora_with_input(
&["-f", "json", "--filter", "e.status >= 400", "--with-stats"],
input,
);
assert_eq!(exit_code, 0, "All-valid input should exit successfully");
let lines: Vec<&str> = stdout.trim().lines().collect();
assert_eq!(lines.len(), 2, "Should emit only the two matching events");
let stats = extract_stats_lines(&stderr);
assert_eq!(
stats_line(&stats, "Lines processed:"),
"Lines processed: 3 total, 0 filtered (0.0%), 0 errors (0.0%)"
);
assert_eq!(
stats_line(&stats, "Events created:"),
"Events created: 3 total, 2 output, 1 filtered (33.3%)"
);
}
#[test]
fn test_error_stats_parallel_vs_sequential_consistency() {
let input = r#"{"valid": "json", "status": 200}
{malformed json line}
{"another": "valid", "status": 404}
not json at all
{"final": "entry", "status": 500}
invalid json again"#;
let (stdout_seq, stderr_seq, exit_code_seq) = run_kelora_with_input(
&["-f", "json", "--filter", "e.status >= 400", "--with-stats"],
input,
);
let (stdout_par, stderr_par, exit_code_par) = run_kelora_with_input(
&[
"-f",
"json",
"--filter",
"e.status >= 400",
"--with-stats",
"--parallel",
"--batch-size",
"2",
],
input,
);
assert_eq!(
exit_code_seq, 1,
"Sequential mode should propagate parse errors via exit status"
);
assert_eq!(
exit_code_par, 1,
"Parallel mode should report errors in resilient mode"
);
let seq_lines: Vec<&str> = stdout_seq.trim().lines().collect();
let par_lines: Vec<&str> = stdout_par.trim().lines().collect();
assert_eq!(
seq_lines.len(),
par_lines.len(),
"Sequential and parallel runs should emit the same number of events"
);
let stats_seq = extract_stats_lines(&stderr_seq);
let stats_par = extract_stats_lines(&stderr_par);
assert_eq!(
stats_line(&stats_seq, "Lines processed:"),
stats_line(&stats_par, "Lines processed:")
);
assert_eq!(
stats_line(&stats_seq, "Events created:"),
stats_line(&stats_par, "Events created:")
);
assert_eq!(
stats_line(&stats_seq, "Lines processed:"),
"Lines processed: 6 total, 0 filtered (0.0%), 3 errors (50.0%)"
);
assert_eq!(
stats_line(&stats_seq, "Events created:"),
"Events created: 3 total, 2 output, 1 filtered (33.3%)"
);
}
#[test]
fn test_error_stats_multiline_mode() {
let input = r#"{"valid": "json", "message": "line1\nline2"}
{malformed json line}
{"another": "valid", "message": "single line"}"#;
let (stdout, stderr, exit_code) = run_kelora_with_input(&["-f", "json", "--with-stats"], input);
assert_eq!(
exit_code, 1,
"Sequential mode should return an error when multiline input has parse failures"
);
let lines: Vec<&str> = stdout.trim().lines().collect();
assert_eq!(lines.len(), 2, "Should emit the two valid JSON events");
let stats = extract_stats_lines(&stderr);
assert_eq!(
stats_line(&stats, "Lines processed:"),
"Lines processed: 3 total, 0 filtered (0.0%), 1 errors (33.3%)"
);
assert_eq!(
stats_line(&stats, "Events created:"),
"Events created: 2 total, 2 output, 0 filtered (0.0%)"
);
let (_stdout_multi, stderr_multi, exit_code_multi) = run_kelora_with_input(
&["-f", "json", "--multiline", "indent", "--with-stats"],
input,
);
assert_eq!(
exit_code_multi, 1,
"Multiline mode should still surface parse errors through the exit status"
);
let stats_multi = extract_stats_lines(&stderr_multi);
assert_eq!(
stats_line(&stats_multi, "Lines processed:"),
"Lines processed: 3 total, 0 filtered (0.0%), 1 errors (33.3%)"
);
assert_eq!(
stats_line(&stats_multi, "Events created:"),
"Events created: 2 total, 2 output, 0 filtered (0.0%)"
);
}