provenance-cli 0.2.3

Command-line interface for Provenance requirements traceability
use assert_cmd::Command;
use serde_json::Value;

#[test]
fn source_update_uses_the_local_writer_and_explicit_clears() {
    let dir = tempfile::tempdir().unwrap();
    let repo = dir.path().to_str().unwrap();
    Command::cargo_bin("provenance")
        .unwrap()
        .args([
            "init",
            "--path",
            repo,
            "--scope",
            "default",
            "--path-prefix",
            ".",
        ])
        .assert()
        .success();
    Command::cargo_bin("provenance")
        .unwrap()
        .args([
            "sources",
            "create",
            "--repo",
            repo,
            "--scope",
            "default",
            "--id",
            "source_one",
            "--name",
            "Policy",
            "--url",
            "https://old.example",
        ])
        .assert()
        .success();
    let output = Command::cargo_bin("provenance")
        .unwrap()
        .args([
            "sources",
            "update",
            "--repo",
            repo,
            "--scope",
            "default",
            "--id",
            "source_one",
            "--fields-json",
            r#"{"reference":"section 2","clear_fields":["url"]}"#,
            "--format",
            "json",
        ])
        .assert()
        .success()
        .get_output()
        .stdout
        .clone();
    let result: Value = serde_json::from_slice(&output).unwrap();
    assert_eq!(result["id"], "source_one");
    assert_eq!(result["name"], "Policy");
    assert_eq!(result["reference"], "section 2");
    assert!(result["url"].is_null());
    Command::cargo_bin("provenance")
        .unwrap()
        .args([
            "sources",
            "update",
            "--repo",
            repo,
            "--scope",
            "default",
            "--id",
            "source_one",
            "--fields-json",
            r#"{"id":"source_other","name":"Wrong"}"#,
        ])
        .assert()
        .failure();
}

#[test]
fn every_descriptive_update_command_uses_the_local_catalog() {
    use serde_json::json;
    let dir = descriptive_records();
    let repo = dir.path().to_str().unwrap();
    for (group, id, patch, field, expected) in [
        (
            "requirements",
            "req_one",
            json!({"description":"Changed"}),
            "description",
            json!("Changed"),
        ),
        (
            "rules",
            "rule_one",
            json!({"name":"Changed","status":"deprecated"}),
            "name",
            json!("Changed"),
        ),
        (
            "resolutions",
            "resolution_one",
            json!({"status":"approved","approved_by":"reviewer","approved_at":1234}),
            "approved_at",
            json!(1234),
        ),
        (
            "domains",
            "domain_one",
            json!({"name":"Changed"}),
            "name",
            json!("Changed"),
        ),
        (
            "boundaries",
            "boundary_one",
            json!({"statement":"Changed"}),
            "statement",
            json!("Changed"),
        ),
        (
            "topics",
            "topic_one",
            json!({"title":"Changed"}),
            "title",
            json!("Changed"),
        ),
        (
            "questions",
            "question_one",
            json!({"question":"Changed?"}),
            "question",
            json!("Changed?"),
        ),
    ] {
        let patch_path = dir.path().join("patch.json");
        std::fs::write(&patch_path, serde_json::to_vec(&patch).unwrap()).unwrap();
        let fields = format!("@{}", patch_path.display());
        let output = Command::cargo_bin("provenance")
            .unwrap()
            .args([
                group,
                "update",
                "--repo",
                repo,
                "--scope",
                "default",
                "--id",
                id,
                "--fields-json",
                &fields,
                "--format",
                "json",
            ])
            .assert()
            .success()
            .get_output()
            .stdout
            .clone();
        let updated: Value = serde_json::from_slice(&output).unwrap();
        assert_eq!(updated["id"], id);
        assert_eq!(updated[field], expected);
    }
}

fn descriptive_records() -> tempfile::TempDir {
    use provenance_store::{layout::ProvenanceLayout, state_store::StateStore};
    use serde_json::json;
    let dir = tempfile::tempdir().unwrap();
    let repo = dir.path().to_str().unwrap();
    Command::cargo_bin("provenance")
        .unwrap()
        .args([
            "init",
            "--path",
            repo,
            "--scope",
            "default",
            "--path-prefix",
            ".",
        ])
        .assert()
        .success();
    let store = StateStore::new(ProvenanceLayout::new(repo));
    store.create_requirement(serde_json::from_value(json!({"scope_id":"default","id":"req_one","statement":"The system saves the record.","status":"active","depends_on":[],"supersedes":[]})).unwrap()).unwrap();
    store.create_rule(serde_json::from_value(json!({"scope_id":"default","id":"rule_one","statement":"The system saves the record.","status":"draft","severity":"medium","requirement_ids":["req_one"],"resolution_ids":[]})).unwrap()).unwrap();
    store.create_resolution(serde_json::from_value(json!({"scope_id":"default","id":"resolution_one","title":"Decision","position":"Use the saved record.","rationale":"The record is available.","status":"draft","requirement_ids":["req_one"],"supersedes":[],"inputs":[]})).unwrap()).unwrap();
    store
        .create_domain(
            serde_json::from_value(json!({"scope_id":"default","id":"domain_one","name":"Domain"}))
                .unwrap(),
        )
        .unwrap();
    store.create_boundary(serde_json::from_value(json!({"scope_id":"default","id":"boundary_one","requirement_id":"req_one","statement":"Limit"})).unwrap()).unwrap();
    store.create_topic(serde_json::from_value(json!({"scope_id":"default","id":"topic_one","requirement_id":"req_one","title":"Topic","status":"open","links":[]})).unwrap()).unwrap();
    store.create_question(serde_json::from_value(json!({"scope_id":"default","id":"question_one","topic_id":"topic_one","question":"Which record?","resolution_method":"research","status":"open","links":[]})).unwrap()).unwrap();
    dir
}