provenance-cli 0.2.3

Command-line interface for Provenance requirements traceability
use assert_cmd::cargo::cargo_bin;
use provenance_macros::verifies;
use serde_json::Value;
use std::path::{Path, PathBuf};
use std::process::Command;

#[test]
#[verifies("rule_nested_rust_implementation_path", examples)]
fn nested_workspace_package_applies_package_relative_implementation_after_init() {
    let fixture = NestedWorkspace::new();
    let root_manifest = std::fs::read(fixture.root().join("Cargo.toml")).unwrap();
    let package_manifest = std::fs::read(fixture.root().join("packages/app/Cargo.toml")).unwrap();
    assert!(!fixture.root().join("Cargo.lock").exists());

    let status = Command::new("cargo")
        .args(["provenance", "init", "--package", "app"])
        .current_dir(fixture.root())
        .env("PATH", NestedWorkspace::path_with_cli())
        .status()
        .expect("run cargo provenance init");
    assert!(status.success());
    assert_eq!(
        std::fs::read(fixture.root().join("Cargo.toml")).unwrap(),
        root_manifest
    );
    assert_eq!(
        std::fs::read(fixture.root().join("packages/app/Cargo.toml")).unwrap(),
        package_manifest
    );
    assert!(!fixture.root().join("Cargo.lock").exists());

    let output = Command::new("cargo")
        .args(["run", "--quiet", "--package", "app"])
        .current_dir(fixture.root())
        .env("CARGO_TARGET_DIR", NestedWorkspace::target_dir())
        .output()
        .expect("build and run nested SDK consumer");
    assert!(
        output.status.success(),
        "consumer failed:\n{}",
        String::from_utf8_lossy(&output.stderr)
    );

    let binding = std::fs::read_to_string(
        fixture
            .root()
            .join(".provenance/state/scopes/default/implementations/binding.jsonl"),
    )
    .expect("implementation binding");
    let binding: Value = serde_json::from_str(binding.trim()).unwrap();
    assert_eq!(binding["file"], "packages/app/src/main.rs");
    assert_eq!(binding["symbol"], "start_workflow");
}

struct NestedWorkspace {
    _temporary: tempfile::TempDir,
    root: PathBuf,
}

impl NestedWorkspace {
    fn new() -> Self {
        let temporary = tempfile::tempdir().unwrap();
        let root = temporary.path().join("workspace");
        let package = root.join("packages/app");
        std::fs::create_dir_all(package.join("src")).unwrap();
        let sdk = Path::new(env!("CARGO_MANIFEST_DIR"))
            .join("../provenance-sdk")
            .to_string_lossy()
            .replace('\\', "/");
        std::fs::write(
            root.join("Cargo.toml"),
            format!(
                "[workspace]\nmembers = [\"packages/app\"]\nresolver = \"2\"\n\n[workspace.dependencies]\nprovenance-sdk = {{ path = \"{sdk}\" }}\n"
            ),
        )
        .unwrap();
        std::fs::write(
            package.join("Cargo.toml"),
            "[package]\nname = \"app\"\nversion = \"0.1.0\"\nedition = \"2021\"\n\n[dependencies]\nprovenance-sdk.workspace = true\n",
        )
        .unwrap();
        std::fs::write(
            package.join("src/main.rs"),
            r#"use provenance_sdk::{implemented_by, operations, requirement, rule, spec, Settings};

fn start_workflow() {}

fn main() -> Result<(), Box<dyn std::error::Error>> {
    start_workflow();
    let settings = Settings::from_env();
    let document = spec("nested-workspace")
        .requirements([requirement("workflow")
            .statement("Accepted workflows start")
            .rules([implemented_by!(
                rule("start").statement("A workflow starts through start_workflow"),
                "src/main.rs",
                start_workflow
            )])])
        .build()?;
    operations::apply(
        settings.repository.clone(),
        &settings.scope_id()?,
        document.materialize(settings.owner),
    )?;
    Ok(())
}
"#,
        )
        .unwrap();
        Self {
            _temporary: temporary,
            root,
        }
    }

    fn root(&self) -> &Path {
        &self.root
    }

    fn path_with_cli() -> std::ffi::OsString {
        let cli = cargo_bin("cargo-provenance");
        std::env::join_paths(std::iter::once(cli.parent().unwrap().to_path_buf()).chain(
            std::env::split_paths(&std::env::var_os("PATH").expect("PATH is set")),
        ))
        .unwrap()
    }

    fn target_dir() -> PathBuf {
        Path::new(env!("CARGO_MANIFEST_DIR")).join("../../target/cargo-nested-sdk")
    }
}