Your browser sucks, JavaScript disabled! Gunnar's console needs JavaScript and WebAssembly. Git over SSH on port 2222 does not.

nordisk/nornir

tests/robot_ops_journeys.rs

//! **Robot-UI ops journeys** — the interactive functions of the ops tabs that
//! aren't just navigation: the 🧬 nornir add-workspace form fields, the 📞 MCP
//! reload, and a nav+drew sweep of the remaining server-ops tabs (Search / Gates /
//! Bench / Warehouse / Release) whose forms need a live server.
//!
//! Run: `cargo test --features "viz" --test robot_ops_journeys`
#![cfg(feature = "viz")]

use std::path::PathBuf;
use std::sync::atomic::{AtomicU64, Ordering};

use egui_kittest::Harness;
use nornir::viz::UrdrThreadsApp;
use nornir_robotui::egui;
use nornir_robotui::{Action, Assert, Observable, RobotSession, Scenario, Step, status};
use serde_json::json;

struct VizApp(UrdrThreadsApp);
impl Observable for VizApp {
    fn state_json(&self) -> serde_json::Value {
        self.0.state_json()
    }
}

static SEQ: AtomicU64 = AtomicU64::new(0);

fn robot(tag: &str) -> RobotSession<'static, VizApp> {
    unsafe { std::env::set_var("NORNIR_VIZ_NO_DURABLE_ACTIONLOG", "1") };
    let n = SEQ.fetch_add(1, Ordering::Relaxed);
    let root = std::env::temp_dir().join(format!("nornir-ops-{tag}-{}-{n}", std::process::id()));
    let _ = std::fs::create_dir_all(&root);
    let app = UrdrThreadsApp::with_repos(
        root,
        "nornir".to_string(),
        PathBuf::new(),
        vec!["nornir".to_string()],
    );
    let harness = Harness::builder()
        .with_size(egui::Vec2::new(1600.0, 1000.0))
        .build_ui_state(|ui, app: &mut VizApp| app.0.draw_ui(ui), VizApp(app));
    let mut r = RobotSession::new(harness, "nornir");
    r.run(4);
    r
}

fn assert_all_pass(rows: &[nornir_robotui::TestResultRow]) {
    for r in rows {
        assert_eq!(
            r.status,
            status::PASS,
            "step {:?} failed: {}",
            r.test_name,
            r.message
        );
    }
}

/// 🧬 nornir root ops — nav + the root-ops surface drew (the add-workspace form
/// fields themselves render only in remote mode against a live server).
#[test]
fn robot_nornir_root_ops_drew() {
    let mut robot = robot("nornir");
    robot.click_label("🧬 nornir").expect("nornir header");
    robot.run(2);
    let scenario = Scenario::new("nornir-root-ops")
        .step(Step::new("on Nornir").assert(Assert::State {
            pointer: "/tab".into(),
            expected: json!("Nornir"),
        }))
        .step(Step::new("root-ops pane drew").assert(Assert::Drew));
    assert_all_pass(&robot.run_scenario(&scenario));
}

/// 📞 MCP — ⟳ Reload flips the loaded flag (reads the mcp_requests stats).
#[test]
fn robot_mcp_reload() {
    let mut robot = robot("mcp");
    robot.click_label("📞 MCP").expect("MCP header");
    robot.run(2);
    let scenario = Scenario::new("mcp-reload")
        .step(Step::new("on Mcp").assert(Assert::State {
            pointer: "/tab".into(),
            expected: json!("Mcp"),
        }))
        .step(
            Step::new("⟳ Reload the mcp_requests stats → loaded")
                .action(Action::ClickLabel("⟳ Reload".into()))
                .assert(Assert::State {
                    pointer: "/mcp/loaded".into(),
                    expected: json!(true),
                }),
        );
    assert_all_pass(&robot.run_scenario(&scenario));
}

/// The server-backed ops tabs (forms need a live server) — nav + it drew, so each
/// surface still fills a robot-ui row from the real header button.
#[test]
fn robot_ops_tabs_nav_and_drew() {
    let mut robot = robot("ops");
    let scenario = Scenario::new("ops-nav")
        .step(nav("🔍 Search", "Search"))
        .step(Step::new("search drew").assert(Assert::Drew))
        .step(nav("🚦 Gates", "Gates"))
        .step(Step::new("gates drew").assert(Assert::Drew))
        .step(nav("🗄 Warehouse", "Warehouse"))
        .step(Step::new("warehouse drew").assert(Assert::Drew))
        .step(nav("🚀 Release", "Release"))
        .step(Step::new("release drew").assert(Assert::Drew));
    assert_all_pass(&robot.run_scenario(&scenario));
}

fn nav(label: &str, id: &str) -> Step {
    Step::new(format!("click {label} → {id}"))
        .action(Action::ClickLabel(label.to_string()))
        .assert(Assert::State {
            pointer: "/tab".into(),
            expected: json!(id),
        })
}