postfix-log-parser 0.2.0

高性能模块化Postfix日志解析器,经3.2GB生产数据验证,SMTPD事件100%准确率
Documentation
//! events/base.rs 模块测试

use chrono::Utc;
use postfix_log_parser::events::base::{
    ComponentEvent, PostfixLogEvent, PostfixLogLevel, UnknownEvent,
};
use postfix_log_parser::{parse_log_line, MasterParser};
use std::fs::{self, File};
use std::io::Write;

/// 从真实日志文件中获取测试数据
fn get_real_log_lines() -> Vec<String> {
    let log_content = fs::read_to_string("logs/test1.log")
        .expect("无法读取 logs/test1.log 文件,请确保日志文件存在");

    let mut lines: Vec<String> = log_content
        .lines()
        .filter(|line| !line.trim().is_empty())
        .map(|line| line.to_string())
        .collect();

    // 如果最后一行没有换行符,需要单独处理
    if !log_content.ends_with('\n') && !log_content.is_empty() {
        if let Some(last_part) = log_content.split('\n').last() {
            if !last_part.trim().is_empty() && !lines.iter().any(|line| line == last_part) {
                lines.push(last_part.to_string());
            }
        }
    }

    lines
}

/// 创建输出目录
fn ensure_output_dir() {
    if let Err(e) = fs::create_dir_all("test_outputs") {
        eprintln!("警告: 无法创建输出目录: {}", e);
    }
}

/// 输出解析结果到文件
fn write_parse_result_to_file(filename: &str, content: &str) {
    ensure_output_dir();
    let filepath = format!("test_outputs/{}", filename);

    match File::create(&filepath) {
        Ok(mut file) => {
            if let Err(e) = file.write_all(content.as_bytes()) {
                eprintln!("警告: 无法写入文件 {}: {}", filepath, e);
            } else {
                println!("✅ 解析结果已输出到: {}", filepath);
            }
        }
        Err(e) => {
            eprintln!("警告: 无法创建文件 {}: {}", filepath, e);
        }
    }
}

#[test]
fn test_log_level_severity() {
    let mut output = String::new();
    output.push_str("=== Events Base - 日志等级严重程度测试结果 ===\n\n");
    output.push_str("测试时间: 2024-12-19\n");

    let levels = vec![
        (PostfixLogLevel::Debug, 1),
        (PostfixLogLevel::Info, 2),
        (PostfixLogLevel::Warning, 3),
        (PostfixLogLevel::Error, 4),
        (PostfixLogLevel::Fatal, 5),
        (PostfixLogLevel::Panic, 6),
    ];

    output.push_str("\n严重程度测试:\n");
    for (level, expected_severity) in &levels {
        let actual_severity = level.severity_level();
        output.push_str(&format!(
            "  {:?}: 期望={}, 实际={}, ✅ 匹配={}\n",
            level,
            expected_severity,
            actual_severity,
            actual_severity == *expected_severity
        ));
        assert_eq!(actual_severity, *expected_severity);
    }

    write_parse_result_to_file("events_base_severity.txt", &output);
}

#[test]
fn test_log_level_methods() {
    let warning = PostfixLogLevel::Warning;
    assert!(warning.is_warning_level());
    assert!(!warning.is_error_level());
    assert!(!warning.is_debug_level());

    let error = PostfixLogLevel::Error;
    assert!(!error.is_warning_level());
    assert!(error.is_error_level());
    assert!(!error.is_debug_level());

    let debug = PostfixLogLevel::Debug;
    assert!(debug.is_debug_level());
    assert!(!debug.is_error_level());
    assert!(!debug.is_warning_level());
}

#[test]
fn test_log_level_from_prefix() {
    assert_eq!(
        PostfixLogLevel::from_prefix("warning:"),
        Some(PostfixLogLevel::Warning)
    );
    assert_eq!(
        PostfixLogLevel::from_prefix("error:"),
        Some(PostfixLogLevel::Error)
    );
    assert_eq!(
        PostfixLogLevel::from_prefix("fatal:"),
        Some(PostfixLogLevel::Fatal)
    );
    assert_eq!(
        PostfixLogLevel::from_prefix("debug:"),
        Some(PostfixLogLevel::Debug)
    );
    assert_eq!(
        PostfixLogLevel::from_prefix("panic:"),
        Some(PostfixLogLevel::Panic)
    );
    assert_eq!(PostfixLogLevel::from_prefix("anything else"), None);
}

#[test]
fn test_log_level_to_string() {
    assert_eq!(PostfixLogLevel::Warning.to_string(), "warning");
    assert_eq!(PostfixLogLevel::Error.to_string(), "error");
    assert_eq!(PostfixLogLevel::Info.to_string(), "info");
    assert_eq!(PostfixLogLevel::Debug.to_string(), "debug");
}

#[test]
fn test_postfix_log_event_creation_from_real_logs() {
    let real_logs = get_real_log_lines();
    let parser = MasterParser::new();

    // 使用真实日志创建PostfixLogEvent
    let log_line = &real_logs[0];
    let base_info = parser.parse_base_info(log_line).unwrap();

    let unknown_event = UnknownEvent {
        component: base_info.component.clone(),
        message: base_info.message.clone(),
    };

    let event = PostfixLogEvent::new(
        log_line.clone(),
        Utc::now(),
        base_info.hostname.clone(),
        base_info.component.clone(),
        base_info.process_id,
        base_info.log_level,
        ComponentEvent::Unknown(unknown_event),
        None,
    );

    assert_eq!(event.hostname, "m01");
    assert_eq!(event.component, "smtpd");
    assert_eq!(event.process_id, 89);
    assert_eq!(event.log_level, PostfixLogLevel::Info);
}

#[test]
fn test_postfix_log_event_convenience_methods_with_real_logs() {
    let real_logs = get_real_log_lines();

    // 找到真实的warning日志
    let warning_log = real_logs
        .iter()
        .find(|line| line.contains("warning:"))
        .expect("应该存在warning级别的日志");

    let result = parse_log_line(warning_log);
    let event = result.event.expect("应该解析成功");

    assert!(event.is_warning_level());
    assert!(!event.is_error_level());
    assert!(!event.is_debug_level());
    assert_eq!(event.severity_level(), 3);
}

#[test]
fn test_real_log_levels_distribution() {
    let real_logs = get_real_log_lines();
    let mut level_counts = std::collections::HashMap::new();
    let mut output = String::new();

    output.push_str("=== Events Base - 真实日志级别分布测试结果 ===\n\n");
    output.push_str("测试时间: 2024-12-19\n");
    output.push_str(&format!("总日志数: {}\n", real_logs.len()));

    output.push_str("\n解析详情:\n");
    let mut parsed_count = 0;

    for (line_num, log_line) in real_logs.iter().enumerate() {
        let result = parse_log_line(log_line);
        if let Some(event) = result.event {
            parsed_count += 1;
            *level_counts.entry(event.log_level.clone()).or_insert(0) += 1;

            // 记录前5行和最后5行的详细信息
            if line_num < 5 || line_num >= real_logs.len() - 5 {
                output.push_str(&format!(
                    "  [{}] {:?} | {} | {}[{}]\n",
                    line_num + 1,
                    event.log_level,
                    event.timestamp.format("%H:%M:%S"),
                    event.component,
                    event.process_id
                ));
            }
        }
    }

    if real_logs.len() > 10 {
        output.push_str(&format!(
            "  ... (省略中间{}行详情) ...\n",
            real_logs.len() - 10
        ));
    }

    output.push_str("\n日志级别分布:\n");
    for (level, count) in &level_counts {
        let percentage = (*count as f64 / parsed_count as f64) * 100.0;
        output.push_str(&format!(
            "  {:?}: {} 条 ({:.1}%)\n",
            level, count, percentage
        ));
    }

    output.push_str(&format!("\n统计结果:\n"));
    output.push_str(&format!("  成功解析: {}\n", parsed_count));
    output.push_str(&format!("  总日志数: {}\n", real_logs.len()));
    output.push_str(&format!(
        "  解析率: {:.2}%\n",
        (parsed_count as f64 / real_logs.len() as f64) * 100.0
    ));

    write_parse_result_to_file("events_base_log_levels_distribution.txt", &output);

    println!("真实日志级别分布:");
    for (level, count) in &level_counts {
        println!("  {:?}: {}", level, count);
    }

    // 验证我们有预期的日志级别
    assert!(level_counts.contains_key(&PostfixLogLevel::Info));
    assert!(level_counts.contains_key(&PostfixLogLevel::Warning));

    // 验证统计结果
    let total: i32 = level_counts.values().sum();
    assert!(total >= 399, "应该有至少399条日志");
}

#[test]
fn test_log_level_enum_comprehensive() {
    // 测试PostfixLogLevel的完整功能
    let all_levels = vec![
        PostfixLogLevel::Debug,
        PostfixLogLevel::Info,
        PostfixLogLevel::Warning,
        PostfixLogLevel::Error,
        PostfixLogLevel::Fatal,
        PostfixLogLevel::Panic,
    ];

    // 测试每个级别的方法
    for level in all_levels {
        // 所有级别都应该有有效的字符串表示
        assert!(!level.to_string().is_empty());
        assert!(!level.as_str().is_empty());

        // 严重程度应该在1-6之间
        let severity = level.severity_level();
        assert!(severity >= 1 && severity <= 6);

        // 测试Display trait
        let display_str = format!("{}", level);
        assert!(!display_str.is_empty());
    }

    // 测试默认值
    assert_eq!(PostfixLogLevel::default(), PostfixLogLevel::Info);
}