Gunnar

nordisk/holger-egui

tests/lineage_feed.rs

//! holger-server-ui **lineage-feed robot-UI** cell — drives the live proxy-cache-filling
//! view-model *headlessly* (no display, no `gui` feature, no server) and asserts
//! its observable `state_json()`. This is the transport-agnostic contract: the
//! model consumes a plain `LineageEvent` feed and maintains the growing
//! node/edge/time-event lineage graph, so the operator's Lineage tab can be driven
//! and asserted as DATA.
//!
//! LAW: inject real input + assert real output. We push real cache-arrival events
//! through the LIVE `mpsc` feed (the same seam a `ChannelAuditLog` audit tee uses)
//! and assert the graph grew with the exact clustered nodes + lineage edges — never
//! merely "didn't panic". (The `AuditEvent → LineageEvent` mapping, the audit tee,
//! and replay-from-audit-dir live as lib unit tests in `src/data/lineage.rs`, where
//! the gated `server_lib::audit` dependency is available.)

use holger_server_ui::data::{channel, demo_events, EdgeKind, LineageAction, LineageEvent, LineageView, NodeKind};

/// Emit one functional-status row into the `nornir test` matrix. Gated behind
/// `--features testmatrix` so the release build strips it entirely.
#[cfg(feature = "testmatrix")]
fn fstatus(component: &str, check: &str, ok: bool, detail: &str) {
    nornir_testmatrix::functional_status(component, check, ok, detail);
}

/// One synthetic download arrival at second `i`.
fn arrival(i: i64, repo: &str, artifact: &str, bytes: u64) -> LineageEvent {
    LineageEvent::new(i * 1_000_000_000, "anonymous", LineageAction::Download, repo, artifact, "10.0.0.5:7000", 200, bytes)
}

/// Robot-UI: drive the live feed and assert the lineage view's `state_json()` grew
/// with clustered artifact nodes + both lineage edges (requested-by / proxied-from)
/// — the operator's Lineage tab, observed as data.
#[test]
fn live_feed_fills_lineage_graph() {
    let mut view = LineageView::new();
    let (tx, rx) = channel();
    view.connect(rx);
    assert!(view.is_live(), "feed attached");

    // Drive: three arrivals across two ecosystems, one a cache hit (serde twice).
    tx.send(arrival(0, "rust-proxy", "serde@1.0.203", 92_160)).unwrap();
    tx.send(arrival(1, "pypi-proxy", "numpy-1.26.4.whl", 5_242_880)).unwrap();
    tx.send(arrival(2, "rust-proxy", "serde@1.0.203", 92_160)).unwrap();
    let drained = view.pump();
    assert_eq!(drained, 3, "pump folded all three live arrivals");

    // Observe: the view's own state_json — exactly what the Lineage tab renders.
    let s = view.state_json();
    assert_eq!(s["total_events"].as_u64(), Some(3), "event tally: {s}");
    assert_eq!(s["total_bytes"].as_u64(), Some(92_160 + 5_242_880 + 92_160), "byte tally: {s}");

    // Two distinct cached artifacts (serde deduped across its two hits).
    assert_eq!(view.artifact_count(), 2, "serde deduped: {s}");
    let nodes = s["nodes"].as_array().expect("nodes array");
    let serde = nodes
        .iter()
        .find(|n| n["label"] == "serde@1.0.203")
        .expect("serde artifact node in state_json");
    assert_eq!(serde["ecosystem"], "rust", "rust cluster derived from repo name");
    assert_eq!(serde["upstream"], "crates.io", "proxied-from origin derived for rust");
    assert!(serde["hits"].as_u64().unwrap_or(0) >= 2, "serde touched twice: {serde}");

    // Both lineage edge kinds are present for a rust download.
    assert!(view
        .edges
        .iter()
        .any(|e| e.kind == EdgeKind::RequestedBy && e.from == "art:rust-proxy/serde@1.0.203" && e.to == "repo:rust-proxy"));
    assert!(view
        .edges
        .iter()
        .any(|e| e.kind == EdgeKind::ProxiedFrom && e.to == "up:crates.io"));

    #[cfg(feature = "testmatrix")]
    fstatus(
        "holger-server-ui",
        "lineage_live_feed_fills",
        drained == 3
            && view.artifact_count() == 2
            && serde["ecosystem"] == "rust"
            && serde["upstream"] == "crates.io",
        &format!("live feed: {drained} arrivals → {} artifacts, serde rust/crates.io", view.artifact_count()),
    );
}

/// Robot-UI: replay-from-history mode folds a whole batch into the same state, and
/// clusters the arrivals by ecosystem (the graph3d grouping / helix lanes).
#[test]
fn replay_batch_clusters_by_ecosystem() {
    let mut view = LineageView::new();
    // Drive: replay the built-in multi-ecosystem demo backlog as one batch.
    view.replay(demo_events());

    let s = view.state_json();
    assert_eq!(s["total_events"].as_u64(), Some(demo_events().len() as u64), "all replayed: {s}");
    // Repeat hits (serde, tokio) deduped → fewer artifact nodes than events.
    assert!(view.artifact_count() < demo_events().len(), "repeat hits deduped");

    // Clustered across at least four ecosystems (rust/pypi/maven/npm/go all appear).
    let clusters: std::collections::HashSet<&str> = view
        .nodes
        .iter()
        .filter(|n| n.kind == NodeKind::Artifact)
        .map(|n| n.ecosystem.label())
        .collect();
    assert!(clusters.len() >= 4, "multi-ecosystem fill: {clusters:?}");

    // Time-ordered events back the helix companion; edges back the 3D fill.
    assert_eq!(view.events.len(), demo_events().len(), "one time-event per arrival");
    assert!(!view.edges.is_empty(), "lineage edges wired");
    assert!(view.events.windows(2).all(|w| w[0].ts_nanos <= w[1].ts_nanos), "events time-ordered");

    #[cfg(feature = "testmatrix")]
    fstatus(
        "holger-server-ui",
        "lineage_replay_clusters",
        view.artifact_count() < demo_events().len() && clusters.len() >= 4,
        &format!("replay: {} events → {} artifacts, {} clusters", view.total_events, view.artifact_count(), clusters.len()),
    );
}