use assert_cmd::Command;
use predicates::prelude::*;
use std::fs;
use std::process::Command as StdCommand;
use tempfile::TempDir;
fn create_test_repo() -> TempDir {
let temp_dir = TempDir::new().unwrap();
StdCommand::new("git")
.args(["init", "-b", "main"])
.current_dir(&temp_dir)
.output()
.unwrap();
StdCommand::new("git")
.args(["config", "user.email", "test@example.com"])
.current_dir(&temp_dir)
.output()
.unwrap();
StdCommand::new("git")
.args(["config", "user.name", "Test User"])
.current_dir(&temp_dir)
.output()
.unwrap();
fs::write(temp_dir.path().join("README.md"), "# Test repo").unwrap();
StdCommand::new("git")
.args(["add", "."])
.current_dir(&temp_dir)
.output()
.unwrap();
StdCommand::new("git")
.args(["commit", "-m", "Initial commit"])
.current_dir(&temp_dir)
.output()
.unwrap();
temp_dir
}
fn create_branch(repo_dir: &std::path::Path, branch_name: &str) {
StdCommand::new("git")
.args(["checkout", "-b", branch_name])
.current_dir(repo_dir)
.output()
.unwrap();
fs::write(
repo_dir.join("test.txt"),
format!("Content for {}", branch_name),
)
.unwrap();
StdCommand::new("git")
.args(["add", "."])
.current_dir(repo_dir)
.output()
.unwrap();
StdCommand::new("git")
.args(["commit", "-m", &format!("Add {} content", branch_name)])
.current_dir(repo_dir)
.output()
.unwrap();
StdCommand::new("git")
.args(["checkout", "main"])
.current_dir(repo_dir)
.output()
.unwrap();
}
fn make_branch_old(repo_dir: &std::path::Path, branch_name: &str, days_old: u32) {
use std::time::{SystemTime, UNIX_EPOCH};
let now = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_secs();
let old_timestamp = now - (days_old as u64 * 86400);
StdCommand::new("git")
.args(["checkout", branch_name])
.current_dir(repo_dir)
.output()
.unwrap();
let date = format!("@{}", old_timestamp);
let output = StdCommand::new("git")
.args(["commit", "--amend", "--no-edit", "--date", &date])
.env("GIT_COMMITTER_DATE", &date)
.current_dir(repo_dir)
.output()
.unwrap();
if !output.status.success() {
eprintln!(
"Warning: git commit --amend failed: {}",
String::from_utf8_lossy(&output.stderr)
);
}
let checkout_main = StdCommand::new("git")
.args(["checkout", "main"])
.current_dir(repo_dir)
.output()
.unwrap();
if !checkout_main.status.success() {
StdCommand::new("git")
.args(["checkout", "master"])
.current_dir(repo_dir)
.output()
.unwrap();
}
}
#[test]
#[allow(deprecated)]
fn test_version() {
Command::cargo_bin("deadbranch")
.unwrap()
.arg("--version")
.assert()
.success()
.stdout(predicate::str::contains("deadbranch"));
}
#[test]
#[allow(deprecated)]
fn test_help() {
Command::cargo_bin("deadbranch")
.unwrap()
.arg("--help")
.assert()
.success()
.stdout(predicate::str::contains(
"Clean up stale git branches safely",
))
.stdout(predicate::str::contains("list"))
.stdout(predicate::str::contains("clean"))
.stdout(predicate::str::contains("config"));
}
#[test]
#[allow(deprecated)]
fn test_not_a_git_repo() {
let temp_dir = TempDir::new().unwrap();
Command::cargo_bin("deadbranch")
.unwrap()
.arg("list")
.current_dir(&temp_dir)
.assert()
.failure()
.code(1);
}
#[test]
#[allow(deprecated)]
fn test_list_empty_repo() {
let repo = create_test_repo();
Command::cargo_bin("deadbranch")
.unwrap()
.arg("list")
.current_dir(&repo)
.assert()
.success()
.stdout(predicate::str::contains("No stale branches found"));
}
#[test]
#[allow(deprecated)]
fn test_list_with_old_branch() {
let repo = create_test_repo();
create_branch(repo.path(), "old-feature");
make_branch_old(repo.path(), "old-feature", 45);
Command::cargo_bin("deadbranch")
.unwrap()
.arg("list")
.current_dir(&repo)
.assert()
.success()
.stdout(predicate::str::contains("old-feature"));
}
#[test]
#[allow(deprecated)]
fn test_list_with_new_branch() {
let repo = create_test_repo();
create_branch(repo.path(), "new-feature");
Command::cargo_bin("deadbranch")
.unwrap()
.arg("list")
.current_dir(&repo)
.assert()
.success()
.stdout(predicate::str::contains("No stale branches found"));
}
#[test]
#[allow(deprecated)]
fn test_list_with_days_filter() {
let repo = create_test_repo();
create_branch(repo.path(), "feature");
make_branch_old(repo.path(), "feature", 5);
Command::cargo_bin("deadbranch")
.unwrap()
.arg("list")
.current_dir(&repo)
.assert()
.success()
.stdout(predicate::str::contains("No stale branches found"));
Command::cargo_bin("deadbranch")
.unwrap()
.args(["list", "--days", "3"])
.current_dir(&repo)
.assert()
.success()
.stdout(predicate::str::contains("feature"));
}
#[test]
#[allow(deprecated)]
fn test_list_local_only() {
let repo = create_test_repo();
create_branch(repo.path(), "local-branch");
make_branch_old(repo.path(), "local-branch", 45);
Command::cargo_bin("deadbranch")
.unwrap()
.args(["list", "--local"])
.current_dir(&repo)
.assert()
.success()
.stdout(predicate::str::contains("local-branch"))
.stdout(predicate::str::contains("Local Branches"));
}
#[test]
#[allow(deprecated)]
fn test_config_show() {
Command::cargo_bin("deadbranch")
.unwrap()
.args(["config", "show"])
.assert()
.success()
.stdout(predicate::str::contains("default_days"))
.stdout(predicate::str::contains("protected"));
}
#[test]
#[allow(deprecated)]
fn test_config_set_default_days() {
Command::cargo_bin("deadbranch")
.unwrap()
.args(["config", "set", "default-days", "45"])
.assert()
.success()
.stdout(predicate::str::contains("Set default-days = 45"));
Command::cargo_bin("deadbranch")
.unwrap()
.args(["config", "set", "default-days", "30"])
.assert()
.success();
}
#[test]
#[allow(deprecated)]
fn test_clean_dry_run() {
let repo = create_test_repo();
create_branch(repo.path(), "old-merged");
make_branch_old(repo.path(), "old-merged", 45);
StdCommand::new("git")
.args(["merge", "old-merged", "--no-ff", "-m", "Merge old-merged"])
.current_dir(&repo)
.output()
.unwrap();
Command::cargo_bin("deadbranch")
.unwrap()
.args(["clean", "--dry-run"])
.current_dir(&repo)
.assert()
.success()
.stdout(predicate::str::contains("DRY RUN"))
.stdout(predicate::str::contains("old-merged"))
.stdout(predicate::str::contains("git branch -d"));
}
#[test]
#[allow(deprecated)]
fn test_clean_requires_confirmation() {
let repo = create_test_repo();
create_branch(repo.path(), "old-merged");
make_branch_old(repo.path(), "old-merged", 45);
StdCommand::new("git")
.args(["merge", "old-merged", "--no-ff", "-m", "Merge old-merged"])
.current_dir(&repo)
.output()
.unwrap();
Command::cargo_bin("deadbranch")
.unwrap()
.args(["clean", "--dry-run"])
.current_dir(&repo)
.assert()
.success()
.stdout(predicate::str::contains("old-merged"));
}
#[test]
#[allow(deprecated)]
fn test_list_respects_protected_branches() {
let repo = create_test_repo();
make_branch_old(repo.path(), "main", 60);
Command::cargo_bin("deadbranch")
.unwrap()
.arg("list")
.current_dir(&repo)
.assert()
.success()
.stdout(predicate::str::contains("No stale branches found"));
}
#[test]
#[allow(deprecated)]
fn test_list_excludes_wip_branches() {
let repo = create_test_repo();
create_branch(repo.path(), "wip/test-feature");
make_branch_old(repo.path(), "wip/test-feature", 45);
Command::cargo_bin("deadbranch")
.unwrap()
.arg("list")
.current_dir(&repo)
.assert()
.success()
.stdout(predicate::str::contains("No stale branches found"));
}
#[test]
#[allow(deprecated)]
fn test_list_excludes_draft_branches() {
let repo = create_test_repo();
create_branch(repo.path(), "feature/draft");
make_branch_old(repo.path(), "feature/draft", 45);
Command::cargo_bin("deadbranch")
.unwrap()
.arg("list")
.current_dir(&repo)
.assert()
.success()
.stdout(predicate::str::contains("No stale branches found"));
}
#[test]
#[allow(deprecated)]
fn test_clean_merged_only_by_default() {
let repo = create_test_repo();
create_branch(repo.path(), "unmerged-old");
make_branch_old(repo.path(), "unmerged-old", 45);
Command::cargo_bin("deadbranch")
.unwrap()
.args(["clean", "--dry-run"])
.current_dir(&repo)
.assert()
.success()
.stdout(
predicate::str::contains("No branches to delete")
.or(predicate::str::contains("unmerged-old").not()),
);
}