facett-lineage/tests/robot.rs
//! **Robot scenario + render proof for the LINEAGE viewer.** Drives the lineage view
//! end-to-end through the inner GRAPH NAVIGATOR's `Msg` surface (query the lineage DAG →
//! select a dataset → highlight a cross-system provenance path → switch layout → export),
//! asserting the lineage `state_json` after each step, then runs a real `egui_kittest`
//! frame so the canvas painter + AccessKit nodes execute (a dataset rides in as a
//! queryable Button).
use egui_kittest::Harness;
use egui_kittest::kittest::{NodeT, Queryable};
use facett_core::Facet;
use facett_lineage::{ExportFormat, LayoutKind, LineageView, Msg};
#[test]
fn robot_drives_the_lineage_dag() {
let mut view = LineageView::demo();
// The lineage summary is exposed before any interaction.
let st = view.state_json();
assert_eq!(st["lineage"]["events"], 6);
assert_eq!(st["lineage"]["datasets"], 5);
assert_eq!(st["lineage"]["runs"], 5);
// 1. Query the DAG for the warehouse-local datasets (label "skade").
view.update(Msg::SetQuery("g.V().hasLabel('skade')".into()));
view.update(Msg::RunQuery);
assert_eq!(view.state_json()["query"]["result_nodes"], 4, "raw/clean/metrics/gold are skade-local");
// 2. Select a dataset node → the inspector names it (caption = the table).
view.update(Msg::SelectNode("main.raw".into()));
assert_eq!(view.state_json()["inspector"]["caption"], "main.raw");
// 3. Highlight the cross-system provenance path: spark events → … → main.gold.
view.update(Msg::SetPath(Some(("spark:app-42/events".into(), "main.gold".into()))));
let path = view.state_json()["path"]["nodes"].as_array().unwrap().len();
assert!(path >= 3, "the ingest→clean→gold provenance chain is found");
// 4. Switch the canvas to the hierarchical (DAG) layout.
view.update(Msg::SetLayout(LayoutKind::Hierarchical));
assert_eq!(view.state_json()["canvas"]["layout"], "hierarchical");
// 5. Export the current lineage graph as DOT.
view.update(Msg::Export(ExportFormat::Dot));
assert_eq!(view.state_json()["export"]["last"], "dot");
}
#[test]
fn canvas_paints_a_dataset_into_the_accesskit_tree() {
let mut view = LineageView::demo();
// Grid keeps the labelled dataset buttons well-separated on the canvas.
view.update(Msg::SetLayout(LayoutKind::Grid));
let mut harness = Harness::builder().with_size(egui::vec2(600.0, 480.0)).build_ui(move |ui| {
view.ui(ui);
});
harness.run();
// A skade dataset rode into the tree as a queryable Button (caption · label).
let node = harness.get_by_label("main.raw · skade").accesskit_node();
assert_eq!(node.role(), egui::accesskit::Role::Button, "a dataset node is a queryable Button");
// The cross-system spark dataset is present too.
assert!(harness.query_by_label("events · spark").is_some());
}
#[test]
fn state_json_is_deterministic_and_carries_lineage() {
let a = LineageView::demo().state_json();
let b = LineageView::demo().state_json();
assert_eq!(a, b, "same lineage ⇒ byte-identical state");
assert!(a["lineage"]["operations"]["append"] == 3);
}
/// **The functional test-matrix cell** — drives the whole lineage-DAG gesture chain
/// and emits one `nornir test` **functional** row per *meaningful* check
/// (`facett_core::testmatrix::emit`, no-op in release / feature off). Before this
/// the Palantir lineage pane was invisible to the matrix — a regression that blanked
/// it showed no red cell. Each row asserts a REAL measured signal (Law 1: never
/// "didn't panic"): the DAG built datasets + runs, a label query narrowed the set, a
/// cross-system provenance path was found, and a DOT export was produced. Mirrors
/// `facett-graphnav`'s emit pattern; the `testmatrix` passthrough feature was already
/// declared in Cargo.toml, so this only supplies the emit the crate had been missing.
#[test]
fn testmatrix_cell_lineage_dag() {
let mut view = LineageView::demo();
// 1. The DAG built: 5 datasets (nodes) and 5 runs (edges) from 6 events.
let st = view.state_json();
let datasets = st["lineage"]["datasets"].as_u64().unwrap_or(0);
let runs = st["lineage"]["runs"].as_u64().unwrap_or(0);
let built = datasets == 5 && runs == 5;
facett_core::testmatrix::emit(
"facett-lineage/dag",
"builds_datasets_and_runs",
built,
&format!("datasets={datasets} runs={runs}"),
);
assert!(built, "lineage DAG built {datasets} datasets / {runs} runs");
// 2. A label query narrows to the 4 warehouse-local (skade) datasets.
view.update(Msg::SetQuery("g.V().hasLabel('skade')".into()));
view.update(Msg::RunQuery);
let hits = view.state_json()["query"]["result_nodes"].as_u64().unwrap_or(0);
let queried = hits == 4;
facett_core::testmatrix::emit(
"facett-lineage/query",
"label_query_narrows_the_dag",
queried,
&format!("hasLabel('skade')→{hits} nodes"),
);
assert!(queried, "the skade-local query matched {hits} datasets");
// 3. The cross-system provenance path (spark ingest → … → main.gold) is found.
view.update(Msg::SetPath(Some(("spark:app-42/events".into(), "main.gold".into()))));
let path_len = view.state_json()["path"]["nodes"].as_array().map(|a| a.len()).unwrap_or(0);
let path_found = path_len >= 3;
facett_core::testmatrix::emit(
"facett-lineage/path",
"cross_system_provenance_path",
path_found,
&format!("ingest→gold chain length={path_len}"),
);
assert!(path_found, "provenance chain length {path_len}");
// 4. A DOT export of the current lineage graph is produced.
view.update(Msg::SetLayout(LayoutKind::Hierarchical));
view.update(Msg::Export(ExportFormat::Dot));
let exported = view.state_json()["export"]["last"] == "dot";
facett_core::testmatrix::emit(
"facett-lineage/export",
"exports_lineage_as_dot",
exported,
"Export(Dot) → export.last=dot",
);
assert!(exported, "lineage exported as DOT");
}