esdiag 0.16.4

Elastic Stack diagnostic collector and processor
use serde_json::Value;
use std::fs;
use std::path::{Path, PathBuf};
use std::process::Command;
use tempfile::{TempDir, tempdir};

fn logstash_archive_fixtures() -> Vec<PathBuf> {
    let archives_dir = Path::new(env!("CARGO_MANIFEST_DIR")).join("tests/archives");
    let mut archives = Vec::new();
    for entry in fs::read_dir(&archives_dir).expect("read tests/archives") {
        let entry = entry.expect("archive dir entry");
        let path = entry.path();
        let filename = path.file_name().and_then(|f| f.to_str()).unwrap_or("");
        if filename.starts_with("logstash-api-diagnostics-") && filename.ends_with(".zip") {
            archives.push(path);
        }
    }
    archives.sort();
    assert!(!archives.is_empty(), "no logstash archive fixtures found");
    archives
}

fn process_archive(archive: &Path, output: &Path, home: &Path) {
    let process = Command::new(env!("CARGO_BIN_EXE_esdiag"))
        .args([
            "process",
            archive.to_str().expect("archive path"),
            output.to_str().expect("output path"),
        ])
        .env("HOME", home)
        .env("USERPROFILE", home)
        .output()
        .expect("run esdiag process");

    assert!(
        process.status.success(),
        "process failed for {}\nstdout:\n{}\nstderr:\n{}",
        archive.display(),
        String::from_utf8_lossy(&process.stdout),
        String::from_utf8_lossy(&process.stderr)
    );
}

fn read_docs(output_dir: &Path, file_name: &str) -> Vec<Value> {
    let path = output_dir.join(file_name);
    let content = fs::read_to_string(&path).unwrap_or_else(|e| panic!("read {} failed: {}", path.display(), e));
    let docs: Vec<Value> = content
        .lines()
        .filter(|line| !line.trim().is_empty())
        .map(|line| {
            serde_json::from_str::<Value>(line)
                .unwrap_or_else(|e| panic!("failed parsing document from {}: {}", path.display(), e))
        })
        .collect();
    assert!(!docs.is_empty(), "{} had no documents", path.display());
    docs
}

fn assert_has_dataset(docs: &[Value], expected_dataset: &str, archive: &Path, stream: &str) {
    assert!(
        docs.iter()
            .any(|doc| doc["data_stream"]["dataset"].as_str() == Some(expected_dataset)),
        "{} in {} did not contain data_stream.dataset={}",
        stream,
        archive.display(),
        expected_dataset
    );
}

fn assert_no_base_logstash_dataset(docs: &[Value], archive: &Path, stream: &str) {
    assert!(
        docs.iter()
            .all(|doc| doc["data_stream"]["dataset"].as_str() != Some("logstash")),
        "{} in {} unexpectedly used broad data_stream.dataset=logstash",
        stream,
        archive.display()
    );
}

#[test]
fn logstash_archive_fixtures_process_across_supported_versions() {
    let home = tempdir().expect("temp HOME");
    let output_root = TempDir::new().expect("temp output");

    for archive in logstash_archive_fixtures() {
        let archive_output = output_root.path().join(
            archive
                .file_stem()
                .and_then(|name| name.to_str())
                .expect("archive file stem"),
        );
        fs::create_dir(&archive_output).expect("archive output dir");
        process_archive(&archive, &archive_output, home.path());

        let settings_node = read_docs(&archive_output, "settings-logstash.node-esdiag.ndjson");
        let metrics_node = read_docs(&archive_output, "metrics-logstash.node-esdiag.ndjson");
        let settings_plugin = read_docs(&archive_output, "settings-logstash.plugin-esdiag.ndjson");

        assert_has_dataset(
            &settings_node,
            "logstash.node",
            &archive,
            "settings-logstash.node-esdiag",
        );
        assert_has_dataset(
            &settings_node,
            "logstash.pipeline",
            &archive,
            "settings-logstash.node-esdiag",
        );
        assert_has_dataset(&metrics_node, "logstash.node", &archive, "metrics-logstash.node-esdiag");
        assert_has_dataset(
            &metrics_node,
            "logstash.pipeline",
            &archive,
            "metrics-logstash.node-esdiag",
        );
        assert_has_dataset(
            &metrics_node,
            "logstash.plugin",
            &archive,
            "metrics-logstash.node-esdiag",
        );
        assert_has_dataset(
            &settings_plugin,
            "logstash.plugin",
            &archive,
            "settings-logstash.plugin-esdiag",
        );
        assert_no_base_logstash_dataset(&settings_node, &archive, "settings-logstash.node-esdiag");
        assert_no_base_logstash_dataset(&metrics_node, &archive, "metrics-logstash.node-esdiag");
        assert_no_base_logstash_dataset(&settings_plugin, &archive, "settings-logstash.plugin-esdiag");
    }
}