provenance-cli 0.2.3

Command-line interface for Provenance requirements traceability
#[path = "query_support/fixtures.rs"]
mod fixtures;

use fixtures::{apply_shared_rule, init_repo, sdk, sdk_error};
use serde_json::json;

#[test]
fn get_returns_one_record_under_a_versioned_envelope() {
    let directory = init_repo();
    let repo = directory.path().to_str().unwrap();
    let ids = apply_shared_rule(&directory);

    let answer = sdk(
        repo,
        "get",
        &json!({"node_type": "rule", "id": ids.rule.as_str()}),
    );

    assert_eq!(
        answer["protocol_version"],
        provenance_core::SDK_PROTOCOL_VERSION
    );
    assert_eq!(answer["operation"], "get");
    assert_eq!(answer["found"], true);
    assert_eq!(answer["node"]["node_type"], "rule");
    assert_eq!(answer["node"]["id"], ids.rule);
    assert_eq!(
        answer["node"]["statement"],
        "Share links expire within 30 days"
    );
}

/// The envelope carries the stamp beside the answer it was read with.
#[test]
fn get_carries_a_stamp_beside_the_answer() {
    let directory = init_repo();
    let repo = directory.path().to_str().unwrap();
    let ids = apply_shared_rule(&directory);

    let (ok, stdout, stderr) = fixtures::sdk_raw(
        repo,
        "get",
        &json!({"node_type": "rule", "id": ids.rule.as_str()}),
    );
    assert!(ok, "{stderr}");
    let answer: serde_json::Value = serde_json::from_str(&stdout).unwrap();
    let stamp = &answer["stamp"];
    assert!(stamp["serial"].as_i64().unwrap() >= 1);
    assert!(stamp["digest"].as_str().unwrap().starts_with("sha256:"));
    assert!(!stamp["instance_id"].as_str().unwrap().is_empty());
    assert!(stamp["derivation"].is_u64());
    assert_eq!(stamp["policy"], "catch_up");
    assert_eq!(
        stamp["attested"],
        json!(["rules"]),
        "get reads the rule from its table"
    );
    assert_eq!(stamp["live"], json!([]), "get reads nothing live");
    assert!(answer.get("freshness_error").is_none());
    assert_eq!(answer["found"], true);
}

#[test]
fn get_hides_a_retired_record_until_the_caller_asks_for_it() {
    let directory = init_repo();
    let repo = directory.path().to_str().unwrap();
    let ids = apply_shared_rule(&directory);
    let mut narrowed = fixtures::shared_rule_spec();
    narrowed["requirements"] = json!([{
        "key": "sharing",
        "statement": "Shares are time bounded",
        "sources": ["retention"]
    }]);
    narrowed["rules"][0]["requirements"] = json!(["sharing"]);
    sdk(repo, "apply", &narrowed);

    let active = sdk(
        repo,
        "get",
        &json!({"node_type": "requirement", "id": ids.sessions.as_str()}),
    );
    assert_eq!(active["found"], false);
    assert!(active.get("node").is_none());

    let including = sdk(
        repo,
        "get",
        &json!({
            "node_type": "requirement",
            "id": ids.sessions.as_str(),
            "include_retired": true
        }),
    );
    assert_eq!(including["found"], true);
    assert_eq!(including["node"]["retired"], true);
    assert_eq!(including["node"]["id"], ids.sessions);
}

#[test]
fn get_refuses_a_request_written_for_another_protocol_version() {
    let directory = init_repo();
    let repo = directory.path().to_str().unwrap();
    let ids = apply_shared_rule(&directory);

    let error = sdk_error(
        repo,
        "get",
        &json!({"protocol_version": 2, "node_type": "rule", "id": ids.rule}),
    );

    let spoken = format!("speaks {}", provenance_core::SDK_PROTOCOL_VERSION);
    assert!(
        error.contains("protocol version 2") && error.contains(&spoken),
        "error should name both versions: {error}"
    );
}

#[test]
fn native_validation_errors_keep_the_original_single_error_line() {
    let directory = init_repo();
    let repo = directory.path().to_str().unwrap();
    for (operation, request, expected) in [
        (
            "search",
            json!({"text":"shared","limit":0}),
            "Error: limit must be between 1 and 200\n".to_owned(),
        ),
        (
            "get",
            json!({"node_type":"rule","id":"rule_missing","protocol_version":6}),
            format!(
                "Error: request names protocol version 6; this engine speaks {}\n",
                provenance_core::SDK_PROTOCOL_VERSION
            ),
        ),
    ] {
        let (ok, stdout, stderr) = fixtures::sdk_raw(repo, operation, &request);
        assert!(!ok);
        assert!(stdout.is_empty());
        assert_eq!(stderr, expected);
    }
}