provenance-cli 0.2.3

Command-line interface for Provenance requirements traceability
use assert_cmd::Command;
use provenance_core::SUPPORTED_SCHEMA_VERSION;

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

    init(&repo);
    create_graph(&repo);

    Command::cargo_bin("provenance")
        .unwrap()
        .args([
            "export",
            "--repo",
            &repo,
            "--scope",
            "default",
            "--format",
            "json",
            "--output",
            &export_path,
        ])
        .assert()
        .success();

    init(&import_repo);
    Command::cargo_bin("provenance")
        .unwrap()
        .args([
            "import",
            "--repo",
            &import_repo,
            "--scope",
            "default",
            "--input",
            &export_path,
            "--dry-run",
            "--format",
            "json",
        ])
        .assert()
        .success()
        .stdout(predicates::str::contains("\"records\""));
    Command::cargo_bin("provenance")
        .unwrap()
        .args([
            "import",
            "--repo",
            &import_repo,
            "--scope",
            "default",
            "--input",
            &export_path,
            "--format",
            "json",
        ])
        .assert()
        .success();
    Command::cargo_bin("provenance")
        .unwrap()
        .args(["check", "--repo", &import_repo, "--format", "json"])
        .assert()
        .success();

    let base = dir.path().join("base.jsonl");
    let ours = dir.path().join("ours.jsonl");
    let theirs = dir.path().join("theirs.jsonl");
    std::fs::write(&base, "").unwrap();
    std::fs::write(&ours, "{\"id\":\"rule_b\"}\n").unwrap();
    std::fs::write(&theirs, "{\"id\":\"rule_a\"}\n").unwrap();

    Command::cargo_bin("provenance")
        .unwrap()
        .args([
            "merge-jsonl",
            &base.to_string_lossy(),
            &ours.to_string_lossy(),
            &theirs.to_string_lossy(),
            "--format",
            "json",
        ])
        .assert()
        .success()
        .stdout(predicates::str::contains("rule_a"));
}

#[allow(clippy::too_many_lines)]
#[test]
fn import_export_roundtrip_preserves_enriched_v1_cloud_fields() {
    let dir = tempfile::tempdir().unwrap();
    let repo = dir.path().join("repo");
    let repo = repo.to_string_lossy().to_string();
    let import_path = dir.path().join("enriched.json");
    let export_path = dir.path().join("export.json");
    let import_path = import_path.to_string_lossy().to_string();
    let export_path = export_path.to_string_lossy().to_string();

    init(&repo);
    std::fs::write(
        &import_path,
        serde_json::json!({
          "scope": "default",
          "sources": [{
            "schema_version": SUPPORTED_SCHEMA_VERSION.0,
            "scope_id": "default",
            "id": "source_sah",
            "name": "Support at Home",
            "source_type": "legislation",
            "url": "https://example.test/sah",
            "reference": "Department guidance"
          }],
          "requirements": [{
            "schema_version": SUPPORTED_SCHEMA_VERSION.0,
            "scope_id": "default",
            "id": "req_sah",
            "statement": "Support at Home shall be traceable",
            "description": "Cloud import description",
            "status": "discovery",
            "source_refs": [{"source_id": "source_sah", "clause": "Program overview"}]
          }],
          "resolutions": [{
            "schema_version": SUPPORTED_SCHEMA_VERSION.0,
            "scope_id": "default",
            "id": "res_sah",
            "title": "SAH extraction",
            "position": "Keep as draft extraction",
            "rationale": "Needs human review",
            "status": "draft",
            "requirement_ids": ["req_sah"],
            "review_on": null,
            "context": "Codebase scan",
            "enforcement": "specification",
            "confidence": 0.91
          }],
          "rules": [{
            "schema_version": SUPPORTED_SCHEMA_VERSION.0,
            "scope_id": "default",
            "id": "rule_sah_001",
            "name": "SAH rule",
            "description": "Rule description",
            "statement": "Draft rule shall stay draft",
            "status": "draft",
            "severity": "high",
            "requirement_ids": ["req_sah"],
            "resolution_ids": ["res_sah"],
            "source_document": "Example-API-main/src/example.php",
            "source_section": "lines 1-3"
          }],
          "threads": [],
          "messages": []
        })
        .to_string(),
    )
    .unwrap();

    Command::cargo_bin("provenance")
        .unwrap()
        .args([
            "import",
            "--repo",
            &repo,
            "--scope",
            "default",
            "--input",
            &import_path,
            "--format",
            "json",
        ])
        .assert()
        .success();
    Command::cargo_bin("provenance")
        .unwrap()
        .args([
            "export",
            "--repo",
            &repo,
            "--scope",
            "default",
            "--format",
            "json",
            "--output",
            &export_path,
        ])
        .assert()
        .success();

    let exported = std::fs::read_to_string(export_path).unwrap();
    assert!(exported.contains(&format!(
        r#""schema_version": {}"#,
        SUPPORTED_SCHEMA_VERSION.0
    )));
    assert!(exported.contains(r#""source_type": "legislation""#));
    assert!(exported.contains(r#""status": "discovery""#));
    assert!(exported.contains(r#""status": "draft""#));
    assert!(exported.contains(r#""source_refs""#));
    assert!(exported.contains(r#""source_document": "Example-API-main/src/example.php""#));
}

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

fn create_graph(repo: &str) {
    Command::cargo_bin("provenance")
        .unwrap()
        .args([
            "sources",
            "create",
            "--repo",
            repo,
            "--scope",
            "default",
            "--id",
            "source_schads",
            "--name",
            "SCHADS Award",
            "--format",
            "json",
        ])
        .assert()
        .success();
    Command::cargo_bin("provenance")
        .unwrap()
        .args([
            "requirements",
            "create",
            "--repo",
            repo,
            "--scope",
            "default",
            "--id",
            "req_schads_overtime",
            "--statement",
            "Overtime must follow SCHADS thresholds",
            "--format",
            "json",
        ])
        .assert()
        .success();
}