provenance-cli 0.2.1

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, Value};

fn at_depth(answer: &Value, depth: u64) -> Vec<String> {
    let mut ids = answer["nodes"]
        .as_array()
        .unwrap()
        .iter()
        .filter(|reached| reached["depth"] == depth)
        .map(|reached| reached["node"]["id"].as_str().unwrap().to_string())
        .collect::<Vec<_>>();
    ids.sort();
    ids
}

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

    let answer = sdk(
        repo,
        "trace",
        &json!({"id": ids.source.as_str(), "direction": "out"}),
    );

    assert_eq!(answer["protocol_version"], 5);
    assert_eq!(answer["operation"], "trace");
    assert_eq!(answer["id"], ids.source);
    assert_eq!(answer["max_depth"], 3);
    let mut requirements = vec![ids.sharing, ids.sessions];
    requirements.sort();
    assert_eq!(at_depth(&answer, 1), requirements);
    assert_eq!(at_depth(&answer, 2), vec![ids.rule]);
}

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

    let answer = sdk(
        repo,
        "trace",
        &json!({"id": ids.source, "direction": "out", "max_depth": 1}),
    );

    assert_eq!(answer["max_depth"], 1);
    assert_eq!(answer["nodes"].as_array().unwrap().len(), 2);
    assert!(at_depth(&answer, 2).is_empty());
}

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

    let answer = sdk(repo, "trace", &json!({"id": ids.rule, "direction": "in"}));

    assert_eq!(at_depth(&answer, 2), vec![ids.source]);
}

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

    let error = sdk_error(repo, "trace", &json!({"id": ids.source, "max_depth": 99}));

    assert!(
        error.contains("max_depth must be between 1 and 10"),
        "error should name the cap: {error}"
    );
}