simit 0.17.4

Semver-aware git commit helper for Rust projects
Documentation
use std::fs;
use std::path::Path;
use std::process::Command;

use serde_json::Value;
use tempfile::TempDir;

mod common;

fn simit() -> Command {
    common::simit()
}

fn fixture_dir(name: &str) -> TempDir {
    let temp = TempDir::new().unwrap();
    copy_dir(
        Path::new("tests/fixtures").join(name).as_path(),
        temp.path(),
    );
    temp
}

fn copy_dir(source: &Path, destination: &Path) {
    fs::create_dir_all(destination).unwrap();
    for entry in fs::read_dir(source).unwrap() {
        let entry = entry.unwrap();
        let source_path = entry.path();
        let destination_path = destination.join(entry.file_name());
        if entry.file_type().unwrap().is_dir() {
            copy_dir(&source_path, &destination_path);
        } else {
            fs::copy(&source_path, &destination_path).unwrap();
        }
    }
}

#[test]
fn release_plan_help_lists_plan_flags() {
    let output = simit()
        .args(["release", "plan", "--help"])
        .output()
        .unwrap();
    assert!(output.status.success());

    let stdout = String::from_utf8(output.stdout).unwrap();
    assert!(stdout.contains("--dry-run-package"));
    assert!(stdout.contains("--package <NAME>"));
    assert!(stdout.contains("--json"));
}

#[test]
fn release_plan_single_crate_repo_prints_single_entry() {
    let cargo_toml =
        fs::read_to_string(Path::new(env!("CARGO_MANIFEST_DIR")).join("Cargo.toml")).unwrap();
    let version_line = cargo_toml
        .lines()
        .find(|line| line.starts_with("version = "))
        .unwrap();
    let version = version_line
        .split('"')
        .nth(1)
        .expect("package version in Cargo.toml");

    let output = simit()
        .current_dir(env!("CARGO_MANIFEST_DIR"))
        .args(["release", "plan"])
        .output()
        .unwrap();

    assert!(output.status.success(), "{output:?}");
    let stdout = String::from_utf8(output.stdout).unwrap();
    assert!(stdout.contains("publish order (1 crates):"));
    assert!(stdout.contains(&format!("1. simit {version}")));
    assert!(stdout.contains("non-publishable members skipped: (none)"));
}

#[test]
fn release_plan_orders_workspace_and_skips_non_publishable_members() {
    let temp = fixture_dir("release-plan-workspace");

    let output = simit()
        .current_dir(temp.path())
        .args(["release", "plan"])
        .output()
        .unwrap();

    assert!(output.status.success(), "{output:?}");
    let stdout = String::from_utf8(output.stdout).unwrap();
    assert!(stdout.contains("publish order (3 crates):"));
    assert!(stdout.contains("1. a 0.1.0"));
    assert!(stdout.contains("2. b 0.1.0"));
    assert!(stdout.contains("3. c 0.1.0"));
    assert!(stdout.contains("non-publishable members skipped: xtask"));
}

#[test]
fn release_plan_json_reports_dependency_order() {
    let temp = fixture_dir("release-plan-workspace");

    let output = simit()
        .current_dir(temp.path())
        .args(["release", "plan", "--json"])
        .output()
        .unwrap();

    assert!(output.status.success(), "{output:?}");
    let value: Value = serde_json::from_slice(&output.stdout).unwrap();
    let entries = value.as_array().unwrap();
    assert_eq!(entries.len(), 3);
    assert_eq!(entries[0]["name"], "a");
    assert_eq!(entries[0]["version"], "0.1.0");
    assert_eq!(entries[0]["publish"], true);
    assert_eq!(entries[0]["depends_on"], Value::Array(Vec::new()));
    assert_eq!(entries[1]["name"], "b");
    assert_eq!(entries[1]["depends_on"], serde_json::json!(["a"]));
    assert_eq!(entries[2]["name"], "c");
    assert_eq!(entries[2]["depends_on"], serde_json::json!(["b"]));
}

#[test]
fn release_plan_dry_run_package_fails_fast_in_publish_order() {
    let temp = fixture_dir("release-plan-dry-run-fail");

    let output = simit()
        .current_dir(temp.path())
        .args(["release", "plan", "--dry-run-package"])
        .output()
        .unwrap();

    assert_eq!(output.status.code(), Some(1));
    let stdout = String::from_utf8(output.stdout).unwrap();
    assert!(stdout.contains("dry-run package: a 0.1.0"));
    assert!(stdout.contains("  ok"));
    assert!(stdout.contains("dry-run package: b 0.1.0"));
    assert!(stdout.contains("  fail"));
    assert!(!stdout.contains("dry-run package: c 0.1.0"));
}