dotrepo-cli 1.0.3

Reference CLI for the dotrepo repository metadata protocol.
Documentation
use std::fs;
use std::path::PathBuf;
use std::process::Command;
use std::time::{SystemTime, UNIX_EPOCH};

fn dotrepo_bin() -> &'static str {
    env!("CARGO_BIN_EXE_dotrepo")
}

fn run_dotrepo(args: &[&str]) -> std::process::Output {
    Command::new(dotrepo_bin())
        .args(args)
        .output()
        .expect("dotrepo command runs")
}

fn temp_dir(label: &str) -> PathBuf {
    let nanos = SystemTime::now()
        .duration_since(UNIX_EPOCH)
        .expect("system time is after unix epoch")
        .as_nanos();
    let root = std::env::temp_dir().join(format!("dotrepo-cli-{label}-{nanos}"));
    fs::create_dir_all(&root).expect("temp dir created");
    root
}

#[test]
fn ci_init_writes_pinned_native_repo_workflow() {
    let root = temp_dir("ci-init");
    fs::write(
        root.join(".repo"),
        r#"
schema = "dotrepo/v0.1"

[record]
mode = "native"
status = "reviewed"

[repo]
name = "example"
description = "Example project"
build = "cargo build --locked"
"#,
    )
    .expect(".repo written");

    let output = run_dotrepo(&[
        "--root",
        root.to_str().expect("temp path is utf-8"),
        "ci",
        "init",
        "--version",
        "1.2.3",
    ]);

    assert!(output.status.success(), "ci init should succeed");
    assert!(output.stderr.is_empty(), "success should not write stderr");

    let workflow =
        fs::read_to_string(root.join(".github/workflows/dotrepo-check.yml")).expect("workflow");
    assert!(workflow.contains("DOTREPO_VERSION: \"1.2.3\""));
    assert!(workflow.contains("https://github.com/maxwellsantoro/dotrepo/releases/download/v1.2.3"));
    assert!(workflow.contains("sha256sum -c"));
    assert!(workflow.contains("dotrepo --root . validate"));
    assert!(workflow.contains("dotrepo --root . query repo.build --raw"));
    assert!(workflow.contains("dotrepo --root . trust"));
    assert!(workflow.contains("dotrepo --root . doctor"));
    assert!(workflow.contains("dotrepo --root . generate --check"));

    fs::remove_dir_all(root).expect("temp dir removed");
}

#[test]
fn ci_init_refuses_to_overwrite_without_force() {
    let root = temp_dir("ci-init-force");
    fs::create_dir_all(root.join(".github/workflows")).expect("workflow dir created");
    fs::write(
        root.join(".repo"),
        r#"
schema = "dotrepo/v0.1"

[record]
mode = "native"
status = "reviewed"

[repo]
name = "example"
description = "Example project"
"#,
    )
    .expect(".repo written");
    fs::write(
        root.join(".github/workflows/dotrepo-check.yml"),
        "name: existing\n",
    )
    .expect("workflow written");

    let output = run_dotrepo(&[
        "--root",
        root.to_str().expect("temp path is utf-8"),
        "ci",
        "init",
    ]);

    assert_eq!(output.status.code(), Some(1));
    let stderr = String::from_utf8(output.stderr).expect("stderr is utf-8");
    assert!(stderr.contains("already exists; rerun with --force"));

    let workflow =
        fs::read_to_string(root.join(".github/workflows/dotrepo-check.yml")).expect("workflow");
    assert_eq!(workflow, "name: existing\n");

    fs::remove_dir_all(root).expect("temp dir removed");
}

#[test]
fn ci_init_refuses_overlay_records() {
    let root = temp_dir("ci-init-overlay");
    fs::write(
        root.join("record.toml"),
        r#"
schema = "dotrepo/v0.1"

[record]
mode = "overlay"
status = "reviewed"
source = "https://github.com/example/project"

[record.trust]
confidence = "medium"
provenance = ["verified"]

[repo]
name = "project"
description = "Example project"
"#,
    )
    .expect("record written");

    let output = run_dotrepo(&[
        "--root",
        root.to_str().expect("temp path is utf-8"),
        "ci",
        "init",
    ]);

    assert_eq!(output.status.code(), Some(2));
    let stderr = String::from_utf8(output.stderr).expect("stderr is utf-8");
    assert!(stderr.contains("ci init is only supported for native records"));

    fs::remove_dir_all(root).expect("temp dir removed");
}

#[test]
fn ci_init_matches_checked_in_native_example_workflow() {
    let root = temp_dir("ci-init-example");
    fs::write(
        root.join(".repo"),
        r#"
schema = "dotrepo/v0.1"

[record]
mode = "native"
status = "reviewed"

[repo]
name = "example"
description = "Example project"
build = "cargo build --locked"
"#,
    )
    .expect(".repo written");

    let output = run_dotrepo(&[
        "--root",
        root.to_str().expect("temp path is utf-8"),
        "ci",
        "init",
    ]);

    assert!(output.status.success(), "ci init should succeed");

    let scaffolded =
        fs::read_to_string(root.join(".github/workflows/dotrepo-check.yml")).expect("workflow");
    let checked_in = fs::read_to_string(
        PathBuf::from(env!("CARGO_MANIFEST_DIR"))
            .join("../../examples/native-minimal/.github/workflows/dotrepo-check.yml"),
    )
    .expect("checked-in example workflow");

    assert_eq!(scaffolded, checked_in);

    fs::remove_dir_all(root).expect("temp dir removed");
}