wash-cli 0.39.0

wasmCloud Shell (wash) CLI tool
Documentation
mod common;

use common::TestWashInstance;

use anyhow::{bail, Context, Result};
use serial_test::serial;
use tokio::process::Command;
use wash_lib::cli::output::{
    GetClaimsCommandOutput, GetHostInventoriesCommandOutput, GetHostsCommandOutput,
    LinkQueryCommandOutput,
};

#[tokio::test]
#[serial]
async fn integration_get_hosts_serial() -> Result<()> {
    let wash_instance = TestWashInstance::create().await?;

    let output = Command::new(env!("CARGO_BIN_EXE_wash"))
        .args([
            "get",
            "hosts",
            "--output",
            "json",
            "--ctl-port",
            &wash_instance.nats_port.to_string(),
        ])
        .kill_on_drop(true)
        .output()
        .await
        .context("failed to execute get hosts")?;

    assert!(output.status.success(), "executed get hosts query");

    let cmd_output: GetHostsCommandOutput = serde_json::from_slice(&output.stdout)?;
    assert!(cmd_output.success, "command returned success");
    assert_eq!(cmd_output.hosts.len(), 1, "hosts contains one host");
    assert_eq!(
        cmd_output.hosts[0].id(),
        wash_instance.host_id,
        "single host ID matches has the wash ID"
    );

    Ok(())
}

#[tokio::test]
#[serial]
async fn integration_get_links_serial() -> Result<()> {
    let wash_instance = TestWashInstance::create().await?;

    let output = Command::new(env!("CARGO_BIN_EXE_wash"))
        .args([
            "get",
            "links",
            "--output",
            "json",
            "--ctl-port",
            &wash_instance.nats_port.to_string(),
        ])
        .kill_on_drop(true)
        .output()
        .await
        .context("failed to execute get links")?;

    assert!(output.status.success(), "executed get links query");

    let cmd_output: LinkQueryCommandOutput = serde_json::from_slice(&output.stdout)?;
    assert!(cmd_output.success, "command returned success");
    assert_eq!(cmd_output.links.len(), 0, "links list is empty");

    Ok(())
}

#[tokio::test]
#[serial]
async fn integration_get_host_inventory_serial() -> Result<()> {
    let wash_instance = TestWashInstance::create().await?;

    let output = Command::new(env!("CARGO_BIN_EXE_wash"))
        .args([
            "get",
            "inventory",
            &wash_instance.host_id,
            "--output",
            "json",
            "--ctl-port",
            &wash_instance.nats_port.to_string(),
        ])
        .kill_on_drop(true)
        .output()
        .await
        .context("failed to execute get inventory")?;

    if !output.status.success() {
        bail!(
            "failed to execute `wash get inventory`, stdout: {} \nstderr: {}",
            String::from_utf8_lossy(&output.stdout),
            String::from_utf8_lossy(&output.stderr),
        );
    }

    let cmd_output: GetHostInventoriesCommandOutput = serde_json::from_slice(&output.stdout)?;
    assert!(cmd_output.success, "command returned success");

    assert!(
        cmd_output.inventories.len() == 1,
        "one host inventory returned"
    );
    let inventory = &cmd_output.inventories[0];
    assert!(
        inventory.components().is_empty(),
        "host inventory contains no components "
    );
    assert_eq!(
        inventory.host_id(),
        wash_instance.host_id,
        "host ID matches request"
    );
    assert!(
        !inventory.labels().is_empty(),
        "at least one label on the host"
    );
    assert!(
        inventory.labels().contains_key("hostcore.os"),
        "hostcore.os label is present"
    );
    assert!(
        inventory.labels().contains_key("hostcore.arch"),
        "hostcore.arch label is present"
    );
    assert!(
        inventory.labels().contains_key("hostcore.osfamily"),
        "hostcore.osfmaily label is present"
    );
    assert!(inventory.providers().is_empty(), "host has no providers");

    Ok(())
}

#[tokio::test]
#[serial]
// TODO: reenable after #1649 merges and v1.0.0-alpha.2 is released
// This issue was fixed in 08bb43a8ae90dc83db653ed78b039479ffe1dd2e
#[ignore]
async fn integration_get_claims_serial() -> Result<()> {
    let wash_instance = TestWashInstance::create().await?;

    let output = Command::new(env!("CARGO_BIN_EXE_wash"))
        .args([
            "get",
            "claims",
            "--output",
            "json",
            "--ctl-port",
            &wash_instance.nats_port.to_string(),
        ])
        .kill_on_drop(true)
        .output()
        .await
        .context("failed to execute get claims")?;

    assert!(output.status.success(), "executed get claims query");

    let cmd_output: GetClaimsCommandOutput = serde_json::from_slice(&output.stdout)?;
    assert!(cmd_output.success, "command returned success");

    Ok(())
}

/// Ensure that labels on host inventories are sorted
#[tokio::test]
#[serial]
async fn integration_get_host_inventory_labels_sorted_serial() -> Result<()> {
    let wash_instance = TestWashInstance::create_with_extra_args(vec![
        "--label", "three=3", "--label", "two=2", "--label", "one=1",
    ])
    .await?;

    let output = Command::new(env!("CARGO_BIN_EXE_wash"))
        .args([
            "get",
            "inventory",
            &wash_instance.host_id,
            "--output",
            "json",
            "--ctl-port",
            &wash_instance.nats_port.to_string(),
        ])
        .kill_on_drop(true)
        .output()
        .await
        .context("failed to execute get inventory")?;

    if !output.status.success() {
        bail!(
            "failed to execute `wash get inventory`, stdout: {} \nstderr: {}",
            String::from_utf8_lossy(&output.stdout),
            String::from_utf8_lossy(&output.stderr),
        );
    }

    let stdout = String::from_utf8_lossy(&output.stdout);
    // Since it's very likely that the output itself will contain lines that
    // have our JSON key, we search for each expected key individually and make sure they came out in an expected (lexicographical order)
    let one_idx = stdout
        .find(r#""one": "1""#)
        .context("missing JSON list entry for one")?;
    let two_idx = stdout
        .find(r#""three": "3""#)
        .context("missing JSON list entry for two")?;
    let three_idx = stdout
        .find(r#""two": "2""#)
        .context("missing JSON list entry for three")?;
    assert!(
        one_idx < two_idx && two_idx < three_idx,
        "one before two before three "
    );
    Ok(())
}