rash_core 2.19.1

Declarative shell scripting using Rust native bindings
Documentation
use std::fs;
use std::path::Path;
use std::process::Command;

use crate::cli::modules::run_test;
use tempfile::tempdir;

fn create_test_repo(path: &Path) {
    fs::create_dir_all(path).unwrap();
    Command::new("git")
        .args(["init"])
        .current_dir(path)
        .output()
        .unwrap();
    Command::new("git")
        .args(["config", "user.email", "test@example.com"])
        .current_dir(path)
        .output()
        .unwrap();
    Command::new("git")
        .args(["config", "user.name", "Test User"])
        .current_dir(path)
        .output()
        .unwrap();
    Command::new("git")
        .args(["commit", "--allow-empty", "-m", "Initial commit"])
        .current_dir(path)
        .output()
        .unwrap();
}

fn add_commit(path: &Path, msg: &str) {
    Command::new("git")
        .args(["commit", "--allow-empty", "-m", msg])
        .current_dir(path)
        .output()
        .unwrap();
}

#[test]
fn test_git_clone() {
    let tmp_dir = tempdir().unwrap();
    let repo_dir = tmp_dir.path().join("source");
    let clone_dir = tmp_dir.path().join("clone");

    create_test_repo(&repo_dir);

    let script_text = format!(
        r#"
#!/usr/bin/env rash
- name: Clone repository
  git:
    repo: {}
    dest: {}
    update: false
        "#,
        repo_dir.to_str().unwrap(),
        clone_dir.to_str().unwrap()
    );

    let args = ["--diff"];
    let (stdout, stderr) = run_test(&script_text, &args);

    assert!(stderr.is_empty(), "stderr: {}", stderr);
    assert!(stdout.contains("changed"), "stdout: {}", stdout);
    assert!(clone_dir.join(".git").exists());
}

#[test]
fn test_git_clone_check_mode() {
    let tmp_dir = tempdir().unwrap();
    let repo_dir = tmp_dir.path().join("source");
    let clone_dir = tmp_dir.path().join("clone");

    create_test_repo(&repo_dir);

    let script_text = format!(
        r#"
#!/usr/bin/env rash
- name: Clone repository in check mode
  git:
    repo: {}
    dest: {}
    update: false
        "#,
        repo_dir.to_str().unwrap(),
        clone_dir.to_str().unwrap()
    );

    let args = ["--check", "--diff"];
    let (stdout, stderr) = run_test(&script_text, &args);

    assert!(stderr.is_empty(), "stderr: {}", stderr);
    assert!(stdout.contains("changed"), "stdout: {}", stdout);
    assert!(!clone_dir.exists(), "Clone should not exist in check mode");
}

#[test]
fn test_git_update_no_changes() {
    let tmp_dir = tempdir().unwrap();
    let repo_dir = tmp_dir.path().join("source");
    let clone_dir = tmp_dir.path().join("clone");

    create_test_repo(&repo_dir);

    Command::new("git")
        .args([
            "clone",
            repo_dir.to_str().unwrap(),
            clone_dir.to_str().unwrap(),
        ])
        .output()
        .unwrap();

    let script_text = format!(
        r#"
#!/usr/bin/env rash
- name: Update repository
  git:
    repo: {}
    dest: {}
    update: true
        "#,
        repo_dir.to_str().unwrap(),
        clone_dir.to_str().unwrap()
    );

    let args = ["--diff"];
    let (stdout, stderr) = run_test(&script_text, &args);

    assert!(stderr.is_empty(), "stderr: {}", stderr);
    assert!(
        stdout.contains("ok"),
        "stdout should contain 'ok': {}",
        stdout
    );
}

#[test]
fn test_git_update_with_changes() {
    let tmp_dir = tempdir().unwrap();
    let repo_dir = tmp_dir.path().join("source");
    let clone_dir = tmp_dir.path().join("clone");

    create_test_repo(&repo_dir);

    Command::new("git")
        .args([
            "clone",
            repo_dir.to_str().unwrap(),
            clone_dir.to_str().unwrap(),
        ])
        .output()
        .unwrap();

    add_commit(&repo_dir, "Second commit");

    let script_text = format!(
        r#"
#!/usr/bin/env rash
- name: Update repository
  git:
    repo: {}
    dest: {}
    update: true
        "#,
        repo_dir.to_str().unwrap(),
        clone_dir.to_str().unwrap()
    );

    let args = ["--diff"];
    let (stdout, stderr) = run_test(&script_text, &args);

    assert!(stderr.is_empty(), "stderr: {}", stderr);
    assert!(stdout.contains("changed"), "stdout: {}", stdout);
}

#[test]
fn test_git_no_update_existing_repo() {
    let tmp_dir = tempdir().unwrap();
    let repo_dir = tmp_dir.path().join("source");
    let clone_dir = tmp_dir.path().join("clone");

    create_test_repo(&repo_dir);

    Command::new("git")
        .args([
            "clone",
            repo_dir.to_str().unwrap(),
            clone_dir.to_str().unwrap(),
        ])
        .output()
        .unwrap();

    add_commit(&repo_dir, "Second commit");

    let script_text = format!(
        r#"
#!/usr/bin/env rash
- name: Do not update repository
  git:
    repo: {}
    dest: {}
    update: false
        "#,
        repo_dir.to_str().unwrap(),
        clone_dir.to_str().unwrap()
    );

    let args = ["--diff"];
    let (stdout, stderr) = run_test(&script_text, &args);

    assert!(stderr.is_empty(), "stderr: {}", stderr);
    assert!(
        stdout.contains("ok"),
        "stdout should contain 'ok': {}",
        stdout
    );
}

#[test]
fn test_git_clone_with_depth() {
    let tmp_dir = tempdir().unwrap();
    let repo_dir = tmp_dir.path().join("source");
    let clone_dir = tmp_dir.path().join("clone");

    create_test_repo(&repo_dir);
    add_commit(&repo_dir, "Second commit");
    add_commit(&repo_dir, "Third commit");

    let script_text = format!(
        r#"
#!/usr/bin/env rash
- name: Shallow clone repository
  git:
    repo: {}
    dest: {}
    depth: 1
    update: false
        "#,
        repo_dir.to_str().unwrap(),
        clone_dir.to_str().unwrap()
    );

    let args = ["--diff"];
    let (stdout, stderr) = run_test(&script_text, &args);

    assert!(stderr.is_empty(), "stderr: {}", stderr);
    assert!(stdout.contains("changed"), "stdout: {}", stdout);
    assert!(clone_dir.join(".git").exists());
}

#[test]
fn test_git_clone_single_branch() {
    let tmp_dir = tempdir().unwrap();
    let repo_dir = tmp_dir.path().join("source");
    let clone_dir = tmp_dir.path().join("clone");

    create_test_repo(&repo_dir);

    let script_text = format!(
        r#"
#!/usr/bin/env rash
- name: Clone single branch
  git:
    repo: {}
    dest: {}
    single_branch: true
    update: false
        "#,
        repo_dir.to_str().unwrap(),
        clone_dir.to_str().unwrap()
    );

    let args = ["--diff"];
    let (stdout, stderr) = run_test(&script_text, &args);

    assert!(stderr.is_empty(), "stderr: {}", stderr);
    assert!(stdout.contains("changed"), "stdout: {}", stdout);
}

#[test]
fn test_git_dest_exists_not_repo() {
    let tmp_dir = tempdir().unwrap();
    let repo_dir = tmp_dir.path().join("source");
    let dest_dir = tmp_dir.path().join("dest");

    create_test_repo(&repo_dir);
    fs::create_dir_all(&dest_dir).unwrap();
    fs::write(dest_dir.join("file.txt"), "content").unwrap();

    let script_text = format!(
        r#"
#!/usr/bin/env rash
- name: Clone to existing non-git directory
  git:
    repo: {}
    dest: {}
    update: false
        "#,
        repo_dir.to_str().unwrap(),
        dest_dir.to_str().unwrap()
    );

    let args = ["--diff"];
    let (_, stderr) = run_test(&script_text, &args);

    assert!(
        stderr.contains("not a git repository"),
        "stderr: {}",
        stderr
    );
}

#[test]
fn test_git_result_extra() {
    let tmp_dir = tempdir().unwrap();
    let repo_dir = tmp_dir.path().join("source");
    let clone_dir = tmp_dir.path().join("clone");

    create_test_repo(&repo_dir);

    let script_text = format!(
        r#"
#!/usr/bin/env rash
- name: Clone repository
  git:
    repo: {}
    dest: {}
    update: false
  register: result
- debug:
    msg: "{{{{ result.extra }}}}"
        "#,
        repo_dir.to_str().unwrap(),
        clone_dir.to_str().unwrap()
    );

    let args = ["--output", "raw"];
    let (stdout, stderr) = run_test(&script_text, &args);

    assert!(stderr.is_empty(), "stderr: {}", stderr);
    assert!(
        stdout.contains("repo"),
        "stdout should contain 'repo': {}",
        stdout
    );
    assert!(
        stdout.contains("dest"),
        "stdout should contain 'dest': {}",
        stdout
    );
}