nornir 0.4.21

Companion to cargo: dependency tracking, release gating, deploy, benchmarks, and documentation assembly. Project-agnostic.
Documentation
//! Inject-and-assert proof for the **robot-UI-tester loop** (viz-introspection /
//! robot-UI-tester epic) — THE_BIG_PLAN LAW #1: feed real input, assert real
//! output, never just "didn't panic".
//!
//! The loop is: an agent writes a command to the viz control channel
//! (`$NORNIR_VIZ_CMD`) via `viz.click`, the running viz polls + applies it next
//! frame, and the result shows up in `state_json()` (which `viz.state` reads from
//! `$NORNIR_VIZ_STATE`). Here we drive that loop **through the real file
//! channel** — `control::write_command` (what the `viz.click` MCP tool calls) →
//! `poll_control_channel` (what the viz runs each frame, here via a `draw_ui`
//! tick) → `state_json()` (what `viz.state` returns) — and assert the Test tab
//! became active AND its rendered rows are present in the returned state.
#![cfg(feature = "viz")]

use std::path::PathBuf;
use std::sync::Mutex;

use nornir::viz::control::{self, VizCommand};
use nornir::viz::UrdrThreadsApp;
use nornir::warehouse::test_results::{status, TestResultRow};

fn mk(run: &str, repo: &str, ts: i64, aspect: &str, name: &str, st: &str) -> TestResultRow {
    TestResultRow {
        run_id: run.into(),
        repo: repo.into(),
        suite: repo.into(),
        test_name: name.into(),
        status: st.into(),
        duration_ms: 4.0,
        ts_micros: ts,
        message: String::new(),
        aspect: aspect.into(),
        metric: 0.0,
    }
}

/// `$NORNIR_VIZ_CMD` (and `$NORNIR_VIZ_ACTIONLOG`/`$NORNIR_VIZ_STATE`) are
/// process-global; these two tests each set their own channel file, so serialize
/// them to keep one from clobbering the other's env while it runs.
static CHANNEL_LOCK: Mutex<()> = Mutex::new(());

fn build_app() -> UrdrThreadsApp {
    let warehouse_root =
        std::env::temp_dir().join(format!("nornir-viz-robot-{}", std::process::id()));
    let _ = std::fs::create_dir_all(&warehouse_root);
    UrdrThreadsApp::with_repos(
        warehouse_root,
        "nornir".to_string(),
        PathBuf::new(),
        vec!["nornir".to_string()],
    )
}

/// Seed a couple of `test_results` rows so the Test tab has REAL rendered rows
/// to read back after the click (not an empty pane).
fn seed_test_rows(app: &mut UrdrThreadsApp) {
    app.inject_test_results_for_test(vec![
        mk("nornir-run", "nornir", 300, "build", "build", status::PASS),
        mk("nornir-run", "nornir", 300, "unit", "wh::a", status::PASS),
    ]);
}

/// One headless frame so `poll_control_channel` runs (it's invoked at the top of
/// `draw_ui`), exactly as the live window would each frame.
fn tick(app: &mut UrdrThreadsApp) {
    let ctx = egui::Context::default();
    let _ = ctx.run(egui::RawInput::default(), |ctx| app.draw_ui(ctx));
}

#[test]
fn robot_loop_clicks_test_tab_through_the_control_channel() {
    let _guard = CHANNEL_LOCK.lock().unwrap_or_else(|p| p.into_inner());
    // Isolate this test's control channel from any real viz / other tests.
    let cmd_file = std::env::temp_dir().join(format!("nornir-viz-cmd-robot-{}.json", std::process::id()));
    std::env::set_var("NORNIR_VIZ_CMD", &cmd_file);
    let _ = std::fs::remove_file(&cmd_file);

    let mut app = build_app();
    seed_test_rows(&mut app);

    // Start somewhere that is NOT the Test tab so the click is observable.
    app.apply_command(&VizCommand { tab: Some("Timeline".into()), workspace: None, palette: None });
    assert_eq!(app.state_json()["tab"].as_str(), Some("Timeline"), "precondition: on Timeline");

    // ── DRIVE: the `viz.click` write-half — drop a command on the channel ─────
    control::write_command(&VizCommand { tab: Some("Test".into()), workspace: None, palette: None })
        .expect("write viz command");
    assert!(cmd_file.exists(), "command file written by viz.click");

    // ── APPLY: one viz frame consumes + applies the command (consume-once) ────
    tick(&mut app);
    assert!(!cmd_file.exists(), "the viz consumed (removed) the command file");

    // ── OBSERVE: the `viz.state` read-half — the Test tab is now active AND its
    //    rendered rows are present in state_json (LAW #6) ─────────────────────
    let state = app.state_json();
    assert_eq!(state["tab"].as_str(), Some("Test"), "click switched to the Test tab");

    let tabs: Vec<&str> = state["all_tabs"].as_array().unwrap().iter().filter_map(|t| t.as_str()).collect();
    assert!(tabs.contains(&"Test"), "Test present in all_tabs");

    // The Test tab's rendered rows came back (the seeded run + its grid).
    let test = &state["test"];
    assert!(test["error"].is_null(), "test pane loaded without error");
    let runs = test["runs"].as_array().expect("test.runs array");
    assert!(!runs.is_empty(), "the seeded run is rendered, got {runs:?}");
    assert_eq!(runs[0]["run_id"].as_str(), Some("nornir-run"), "the seeded run id surfaces");
    let grid_repos: Vec<&str> = test["grid"]["repos"].as_array().unwrap().iter().filter_map(|r| r.as_str()).collect();
    assert_eq!(grid_repos, vec!["nornir"], "the grid renders the seeded repo row");

    // The drive is auditable: the click landed in the action trail (same log as a
    // human click), proving the command went through apply_command, not a bypass.
    let trail: Vec<String> = state["action_log"]["recent"].as_array().unwrap()
        .iter().filter_map(|e| e.as_str().map(String::from)).collect();
    assert!(
        trail.iter().any(|l| l.contains("viz.click tab → Test")),
        "the viz.click drive is in the action trail: {trail:?}"
    );

    let _ = std::fs::remove_file(&cmd_file);
}

#[test]
fn robot_loop_switches_workspace_via_command() {
    let _guard = CHANNEL_LOCK.lock().unwrap_or_else(|p| p.into_inner());
    let cmd_file = std::env::temp_dir().join(format!("nornir-viz-cmd-ws-{}.json", std::process::id()));
    std::env::set_var("NORNIR_VIZ_CMD", &cmd_file);
    let _ = std::fs::remove_file(&cmd_file);

    let mut app = build_app();
    assert_eq!(app.state_json()["workspace_picker"]["selected"].as_str(), Some("nornir"));

    // A workspace the local app doesn't have still gets selected (the picker
    // honours an explicit name); the timeline load just errors, which is fine —
    // we're asserting the *control channel applied the selection*.
    control::write_command(&VizCommand { tab: None, workspace: Some("korp".into()), palette: None })
        .expect("write viz command");
    tick(&mut app);

    let state = app.state_json();
    assert_eq!(
        state["workspace_picker"]["selected"].as_str(),
        Some("korp"),
        "viz.click switched the selected workspace"
    );

    let _ = std::fs::remove_file(&cmd_file);
}