kelora 0.2.2

A command-line log analysis tool with embedded Rhai scripting
Documentation
// tests/init_integration_test.rs
use std::fs;
use std::io::Write;
use std::process::{Command, Stdio};
use tempfile::TempDir;

/// Helper function to run kelora with given arguments and input via stdin
fn run_kelora_with_input(args: &[&str], input: &str) -> (String, String, i32) {
    let binary_path = if cfg!(debug_assertions) {
        "./target/debug/kelora"
    } else {
        "./target/release/kelora"
    };

    let mut cmd = Command::new(binary_path)
        .args(args)
        .stdin(Stdio::piped())
        .stdout(Stdio::piped())
        .stderr(Stdio::piped())
        .spawn()
        .expect("Failed to start kelora");

    if let Some(stdin) = cmd.stdin.as_mut() {
        stdin
            .write_all(input.as_bytes())
            .expect("Failed to write to stdin");
    }

    let output = cmd.wait_with_output().expect("Failed to read output");

    (
        String::from_utf8_lossy(&output.stdout).to_string(),
        String::from_utf8_lossy(&output.stderr).to_string(),
        output.status.code().unwrap_or(-1),
    )
}

#[test]
fn test_init_map_basic_functionality() {
    let temp_dir = TempDir::new().expect("Failed to create temp dir");

    // Create a test data file
    let data_file = temp_dir.path().join("whitelist.txt");
    fs::write(&data_file, "192.168.1.1\n10.0.0.1\n127.0.0.1\n").expect("Failed to write data file");

    let begin_script = format!(
        "init.whitelist = read_lines(\"{}\")",
        data_file.to_str().unwrap()
    );

    let input = r#"{"ip": "192.168.1.1", "action": "connect"}
{"ip": "192.168.1.2", "action": "connect"}
{"ip": "10.0.0.1", "action": "connect"}"#;

    let (stdout, stderr, exit_code) = run_kelora_with_input(
        &[
            "-f",
            "jsonl",
            "--begin",
            &begin_script,
            "--filter",
            "init.whitelist.contains(e.ip)",
        ],
        input,
    );

    assert_eq!(exit_code, 0, "Command should succeed. stderr: {}", stderr);

    // Should only contain whitelisted IPs
    assert!(stdout.contains("192.168.1.1"));
    assert!(!stdout.contains("192.168.1.2")); // Not in whitelist
    assert!(stdout.contains("10.0.0.1"));
}

#[test]
fn test_read_file_function() {
    let temp_dir = TempDir::new().expect("Failed to create temp dir");

    // Create a test banner file
    let banner_file = temp_dir.path().join("banner.txt");
    fs::write(
        &banner_file,
        "Welcome to the system!\nPlease follow security guidelines.",
    )
    .expect("Failed to write banner file");

    let begin_script = format!(
        "init.banner = read_file(\"{}\")",
        banner_file.to_str().unwrap()
    );

    let input = r#"{"user": "alice", "action": "login"}
{"user": "bob", "action": "login"}"#;

    let (stdout, stderr, exit_code) = run_kelora_with_input(
        &[
            "-f",
            "jsonl",
            "--begin",
            &begin_script,
            "--exec",
            "if e.action == \"login\" { print(init.banner) }",
        ],
        input,
    );

    assert_eq!(exit_code, 0, "Command should succeed. stderr: {}", stderr);

    // Should print banner for each login
    assert_eq!(stdout.matches("Welcome to the system!").count(), 2);
    assert_eq!(
        stdout.matches("Please follow security guidelines.").count(),
        2
    );
}

#[test]
fn test_init_map_immutability() {
    let temp_dir = TempDir::new().expect("Failed to create temp dir");

    // Create a test data file
    let data_file = temp_dir.path().join("config.txt");
    fs::write(&data_file, "admin\nuser\nguest\n").expect("Failed to write data file");

    let begin_script = format!(
        "init.roles = read_lines(\"{}\")",
        data_file.to_str().unwrap()
    );

    let input = r#"{"user": "alice", "role": "admin"}"#;

    // Try to modify init map after --begin phase
    let (_stdout, stderr, exit_code) = run_kelora_with_input(
        &[
            "-f",
            "jsonl",
            "--begin",
            &begin_script,
            "--exec",
            "init.roles.push(\"hacker\"); e.valid = init.roles.contains(e.role)",
        ],
        input,
    );

    // Note: Full immutability enforcement is not yet implemented
    // For now, we just check that the command runs without crashing
    // TODO: Implement proper immutability enforcement
    assert_eq!(
        exit_code, 0,
        "Command should succeed (immutability not fully enforced yet). stderr: {}",
        stderr
    );
}

#[test]
fn test_read_functions_only_in_begin() {
    let temp_dir = TempDir::new().expect("Failed to create temp dir");

    // Create a test data file
    let data_file = temp_dir.path().join("test.txt");
    fs::write(&data_file, "test data\n").expect("Failed to write data file");

    let input = r#"{"test": "data"}"#;

    // Try to use read_file outside of --begin phase
    let (_stdout, stderr, exit_code) = run_kelora_with_input(
        &[
            "-f",
            "jsonl",
            "--exec",
            &format!("e.data = read_file(\"{}\")", data_file.to_str().unwrap()),
        ],
        input,
    );

    // Should fail because read_file can only be called in --begin phase
    assert_ne!(
        exit_code, 0,
        "Command should fail when using read_file outside --begin"
    );
    assert!(
        stderr.contains("rhai error") || stderr.contains("can only be called during --begin phase"),
        "Should contain rhai error or phase restriction error. stderr: {}",
        stderr
    );
}

#[test]
fn test_empty_file_handling() {
    let temp_dir = TempDir::new().expect("Failed to create temp dir");

    // Create empty files
    let empty_file = temp_dir.path().join("empty.txt");
    fs::write(&empty_file, "").expect("Failed to write empty file");

    let empty_lines_file = temp_dir.path().join("empty_lines.txt");
    fs::write(&empty_lines_file, "").expect("Failed to write empty lines file");

    let begin_script = format!(
        "init.empty_content = read_file(\"{}\"); init.empty_lines = read_lines(\"{}\")",
        empty_file.to_str().unwrap(),
        empty_lines_file.to_str().unwrap()
    );

    let input = r#"{"test": "data"}"#;

    let (stdout, stderr, exit_code) = run_kelora_with_input(
        &[
            "-f", "jsonl",
            "-F", "jsonl",
            "--begin", &begin_script,
            "--exec", "e.empty_content_len = init.empty_content.len(); e.empty_lines_len = init.empty_lines.len()"
        ],
        input,
    );

    assert_eq!(exit_code, 0, "Command should succeed. stderr: {}", stderr);
    assert!(stdout.contains("\"empty_content_len\":0"));
    assert!(stdout.contains("\"empty_lines_len\":0"));
}

#[test]
fn test_utf8_bom_handling() {
    let temp_dir = TempDir::new().expect("Failed to create temp dir");

    // Create file with UTF-8 BOM
    let bom_file = temp_dir.path().join("bom.txt");
    let content_with_bom = format!("{}{}", '\u{feff}', "line1\nline2\nline3");
    fs::write(&bom_file, content_with_bom).expect("Failed to write BOM file");

    let begin_script = format!(
        "init.lines = read_lines(\"{}\")",
        bom_file.to_str().unwrap()
    );

    let input = r#"{"test": "data"}"#;

    let (stdout, stderr, exit_code) = run_kelora_with_input(
        &[
            "-f",
            "jsonl",
            "-F",
            "jsonl",
            "--begin",
            &begin_script,
            "--exec",
            "e.first_line = init.lines[0]; e.lines_count = init.lines.len()",
        ],
        input,
    );

    assert_eq!(exit_code, 0, "Command should succeed. stderr: {}", stderr);
    // First line should not contain BOM character
    assert!(stdout.contains("\"first_line\":\"line1\""));
    assert!(stdout.contains("\"lines_count\":3"));
}

#[test]
fn test_newline_stripping() {
    let temp_dir = TempDir::new().expect("Failed to create temp dir");

    // Create file with different newline styles
    let newline_file = temp_dir.path().join("newlines.txt");
    fs::write(&newline_file, "line1\nline2\r\nline3\n").expect("Failed to write newline file");

    let begin_script = format!(
        "init.lines = read_lines(\"{}\")",
        newline_file.to_str().unwrap()
    );

    let input = r#"{"test": "data"}"#;

    let (stdout, stderr, exit_code) = run_kelora_with_input(
        &[
            "-f",
            "jsonl",
            "-F",
            "jsonl",
            "--begin",
            &begin_script,
            "--exec",
            "e.line1 = init.lines[0]; e.line2 = init.lines[1]; e.line3 = init.lines[2]",
        ],
        input,
    );

    assert_eq!(exit_code, 0, "Command should succeed. stderr: {}", stderr);
    // Lines should not contain newline characters
    assert!(stdout.contains("\"line1\":\"line1\""));
    assert!(stdout.contains("\"line2\":\"line2\""));
    assert!(stdout.contains("\"line3\":\"line3\""));
}