provenance-cli 0.2.3

Command-line interface for Provenance requirements traceability
use assert_cmd::Command;
use predicates::str::contains;

#[test]
fn cli_domains_roundtrip_materialize_and_export() {
    let dir = tempfile::tempdir().unwrap();
    let repo = dir.path().join("repo").to_string_lossy().to_string();
    let import_repo = dir.path().join("imported").to_string_lossy().to_string();
    let export_path = dir.path().join("export.json").to_string_lossy().to_string();
    let import_export_path = dir
        .path()
        .join("import-export.json")
        .to_string_lossy()
        .to_string();

    init(&repo);
    create_domain(&repo);
    create_requirement(&repo);
    create_rule(&repo);
    verify_materialized_lists(&repo);
    export_scope(&repo, &export_path);
    assert_export_contains_domain_records(&export_path);
    import_export_roundtrip(&import_repo, &export_path, &import_export_path);
}

fn provenance(args: &[&str]) -> assert_cmd::assert::Assert {
    Command::cargo_bin("provenance")
        .unwrap()
        .args(args)
        .assert()
}

fn init(repo: &str) {
    provenance(&[
        "init",
        "--path",
        repo,
        "--scope",
        "default",
        "--path-prefix",
        ".",
    ])
    .success();
}

fn create_domain(repo: &str) {
    provenance(&[
        "domains",
        "create",
        "--repo",
        repo,
        "--scope",
        "default",
        "--id",
        "domain_payroll",
        "--name",
        "Payroll",
        "--description",
        "Payroll compliance",
        "--color",
        "#3b82f6",
        "--format",
        "json",
    ])
    .success()
    .stdout(contains("domain_payroll"))
    .stdout(contains(r##""color": "#3b82f6""##));
}

fn create_requirement(repo: &str) {
    provenance(&[
        "requirements",
        "create",
        "--repo",
        repo,
        "--scope",
        "default",
        "--id",
        "req_overtime",
        "--statement",
        "Overtime must be traceable",
        "--status",
        "discovery",
        "--domain-id",
        "domain_payroll",
        "--format",
        "json",
    ])
    .success()
    .stdout(contains(r#""domain_id": "domain_payroll""#));
}

fn create_rule(repo: &str) {
    provenance(&[
        "rules",
        "create",
        "--repo",
        repo,
        "--scope",
        "default",
        "--id",
        "rule_overtime",
        "--requirement-id",
        "req_overtime",
        "--statement",
        "Pay overtime after threshold",
        "--status",
        "active",
        "--severity",
        "high",
        "--format",
        "json",
    ])
    .success();
}

fn verify_materialized_lists(repo: &str) {
    provenance(&["materialize", "--repo", repo, "--format", "json"])
        .success()
        .stdout(contains(r#""records_loaded""#));
    provenance(&[
        "domains", "list", "--repo", repo, "--scope", "default", "--format", "json",
    ])
    .success()
    .stdout(contains("domain_payroll"));
    provenance(&[
        "rules", "list", "--repo", repo, "--scope", "default", "--format", "json",
    ])
    .success()
    .stdout(contains("rule_overtime"));
}

fn export_scope(repo: &str, export_path: &str) {
    provenance(&[
        "export",
        "--repo",
        repo,
        "--scope",
        "default",
        "--format",
        "json",
        "--output",
        export_path,
    ])
    .success();
}

fn assert_export_contains_domain_records(export_path: &str) {
    let exported = std::fs::read_to_string(export_path).unwrap();
    assert!(exported.contains(r#""domains""#));
    assert!(exported.contains("domain_payroll"));
    assert!(!exported.contains(r#""services""#));
    assert!(!exported.contains(r#""service_bindings""#));
}

fn import_export_roundtrip(import_repo: &str, export_path: &str, import_export_path: &str) {
    init(import_repo);
    provenance(&[
        "import",
        "--repo",
        import_repo,
        "--scope",
        "default",
        "--input",
        export_path,
        "--format",
        "json",
    ])
    .success();
    export_scope(import_repo, import_export_path);

    let imported_export = std::fs::read_to_string(import_export_path).unwrap();
    assert!(imported_export.contains("domain_payroll"));
    assert!(imported_export.contains("rule_overtime"));
}