assay-lua 0.19.0

General-purpose enhanced Lua runtime. Batteries-included scripting, automation, and web services.
Documentation
use serde_json::Value;
use std::process::Command;

fn assay_bin() -> Command {
    Command::new(env!("CARGO_BIN_EXE_assay"))
}

fn modules_json() -> Value {
    let output = assay_bin().args(["modules", "--json"]).output().unwrap();
    assert!(output.status.success(), "modules --json should exit 0");
    serde_json::from_slice(&output.stdout).unwrap_or_else(|e| {
        panic!(
            "modules --json should emit valid JSON: {e}: {}",
            String::from_utf8_lossy(&output.stdout)
        )
    })
}

fn module_named<'a>(payload: &'a Value, name: &str) -> &'a Value {
    payload["modules"]
        .as_array()
        .unwrap()
        .iter()
        .find(|m| m["name"] == name)
        .unwrap_or_else(|| panic!("missing module {name} in {payload}"))
}

#[test]
fn test_modules_exits_zero() {
    let output = assay_bin().arg("modules").output().unwrap();
    assert!(output.status.success(), "modules should exit 0");
}

#[test]
fn test_modules_lists_grafana() {
    let output = assay_bin().arg("modules").output().unwrap();
    let stdout = String::from_utf8_lossy(&output.stdout);
    assert!(
        stdout.contains("assay.grafana"),
        "should list assay.grafana: {stdout}"
    );
}

#[test]
fn test_modules_lists_http_builtin() {
    let output = assay_bin().arg("modules").output().unwrap();
    let stdout = String::from_utf8_lossy(&output.stdout);
    assert!(
        stdout.contains("http"),
        "should list http builtin: {stdout}"
    );
}

#[test]
fn test_modules_has_header() {
    let output = assay_bin().arg("modules").output().unwrap();
    let stdout = String::from_utf8_lossy(&output.stdout);
    assert!(
        stdout.contains("MODULE"),
        "should have MODULE header: {stdout}"
    );
}

#[test]
fn test_modules_lists_vault() {
    let output = assay_bin().arg("modules").output().unwrap();
    let stdout = String::from_utf8_lossy(&output.stdout);
    assert!(
        stdout.contains("assay.hashicorp.vault"),
        "should list assay.hashicorp.vault: {stdout}"
    );
    assert!(
        stdout.contains("assay.engine.vault"),
        "should list assay.engine.vault: {stdout}"
    );
}

#[test]
fn test_modules_json_envelope_carries_binary_version() {
    let reported = assay_bin().arg("--version").output().unwrap();
    let reported = String::from_utf8_lossy(&reported.stdout);
    let reported = reported.split_whitespace().last().unwrap();

    let payload = modules_json();
    assert_eq!(
        payload["version"],
        Value::from(reported),
        "consumers cache-bust on this, so it must track the binary: {payload}"
    );
    assert!(
        payload["modules"].as_array().unwrap().len() > 1,
        "expected the discovered modules: {payload}"
    );
}

#[test]
fn test_modules_json_carries_metadata_the_table_drops() {
    let payload = modules_json();
    let grafana = module_named(&payload, "assay.grafana");

    assert_eq!(grafana["source"], "builtin");
    assert!(
        grafana["description"]
            .as_str()
            .is_some_and(|d| !d.is_empty()),
        "grafana: {grafana}"
    );
    assert!(
        grafana["keywords"]
            .as_array()
            .is_some_and(|k| k.contains(&Value::from("grafana"))),
        "keywords drive search and must be exposed: {grafana}"
    );
    assert!(
        grafana["auto_functions"]
            .as_array()
            .is_some_and(|f| !f.is_empty()),
        "auto_functions: {grafana}"
    );

    let quickref = &grafana["quickrefs"][0];
    for field in ["signature", "return_hint", "description"] {
        assert!(
            quickref[field].as_str().is_some_and(|v| !v.is_empty()),
            "quickref.{field} missing: {quickref}"
        );
    }
}

#[test]
fn test_modules_json_carries_env_vars() {
    let payload = modules_json();
    let k8s = module_named(&payload, "assay.k8s");
    assert!(
        k8s["env_vars"]
            .as_array()
            .is_some_and(|e| e.contains(&Value::from("KUBECONFIG"))),
        "env vars tell a caller what the module needs configured: {k8s}"
    );
}

#[test]
fn test_modules_json_carries_icon_and_category() {
    let payload = modules_json();
    let k8s = module_named(&payload, "assay.k8s");
    assert_eq!(k8s["category"], Value::from("kubernetes"), "k8s: {k8s}");
    assert_eq!(k8s["icon"], Value::from("kubernetes"), "k8s: {k8s}");
}

#[test]
fn test_modules_json_emits_null_icon_for_unbranded_module() {
    let payload = modules_json();
    let velero = module_named(&payload, "assay.velero");
    assert_eq!(
        velero["category"],
        Value::from("kubernetes"),
        "velero: {velero}"
    );
    assert_eq!(
        velero["icon"],
        Value::Null,
        "an unbranded module must emit a null a consumer can fall back on, not be omitted: {velero}"
    );
}

#[test]
fn test_modules_table_unaffected_by_json_support() {
    let output = assay_bin().arg("modules").output().unwrap();
    let stdout = String::from_utf8_lossy(&output.stdout);
    assert!(
        stdout.starts_with("MODULE"),
        "default output stays the table: {stdout}"
    );
    assert!(
        !stdout.contains("\"modules\""),
        "no JSON without --json: {stdout}"
    );
}

#[test]
fn test_modules_shows_source_column() {
    let output = assay_bin().arg("modules").output().unwrap();
    let stdout = String::from_utf8_lossy(&output.stdout);
    assert!(
        stdout.contains("SOURCE"),
        "should have SOURCE header: {stdout}"
    );
    assert!(
        stdout.contains("builtin"),
        "should show builtin source label: {stdout}"
    );
}