#![cfg(feature = "git")]
use bashkit::{Bash, GitConfig};
fn create_git_bash() -> Bash {
Bash::builder()
.git(
GitConfig::new()
.author("Test User", "test@example.com")
.allow_all_remotes(),
)
.build()
}
async fn setup_repo_with_commit(bash: &mut Bash) {
bash.exec("git init /repo").await.unwrap();
bash.exec("echo 'hello' > /repo/README.md").await.unwrap();
bash.exec("cd /repo && git add README.md").await.unwrap();
bash.exec("cd /repo && git commit -m 'Initial commit'")
.await
.unwrap();
}
mod branch_operations {
use super::*;
#[tokio::test]
async fn test_branch_list_default() {
let mut bash = create_git_bash();
setup_repo_with_commit(&mut bash).await;
let result = bash.exec("cd /repo && git branch").await.unwrap();
assert_eq!(result.exit_code, 0);
assert!(result.stdout.contains("* master"));
}
#[tokio::test]
async fn test_branch_create() {
let mut bash = create_git_bash();
setup_repo_with_commit(&mut bash).await;
let result = bash.exec("cd /repo && git branch feature").await.unwrap();
assert_eq!(result.exit_code, 0);
let result = bash.exec("cd /repo && git branch").await.unwrap();
assert!(result.stdout.contains("* master"));
assert!(result.stdout.contains("feature"));
}
#[tokio::test]
async fn test_branch_create_duplicate() {
let mut bash = create_git_bash();
setup_repo_with_commit(&mut bash).await;
bash.exec("cd /repo && git branch feature").await.unwrap();
let result = bash.exec("cd /repo && git branch feature").await.unwrap();
assert_ne!(result.exit_code, 0);
assert!(result.stderr.contains("already exists"));
}
#[tokio::test]
async fn test_branch_delete() {
let mut bash = create_git_bash();
setup_repo_with_commit(&mut bash).await;
bash.exec("cd /repo && git branch feature").await.unwrap();
let result = bash
.exec("cd /repo && git branch -d feature")
.await
.unwrap();
assert_eq!(result.exit_code, 0);
assert!(result.stdout.contains("Deleted branch feature"));
let result = bash.exec("cd /repo && git branch").await.unwrap();
assert!(!result.stdout.contains("feature"));
}
#[tokio::test]
async fn test_branch_delete_current() {
let mut bash = create_git_bash();
setup_repo_with_commit(&mut bash).await;
let result = bash.exec("cd /repo && git branch -d master").await.unwrap();
assert_ne!(result.exit_code, 0);
assert!(result.stderr.contains("cannot delete branch"));
}
#[tokio::test]
async fn test_branch_delete_nonexistent() {
let mut bash = create_git_bash();
setup_repo_with_commit(&mut bash).await;
let result = bash
.exec("cd /repo && git branch -d nonexistent")
.await
.unwrap();
assert_ne!(result.exit_code, 0);
assert!(result.stderr.contains("not found"));
}
#[tokio::test]
async fn test_branch_no_commits() {
let mut bash = create_git_bash();
bash.exec("git init /repo").await.unwrap();
let result = bash.exec("cd /repo && git branch feature").await.unwrap();
assert_ne!(result.exit_code, 0);
assert!(result.stderr.contains("not a valid object name"));
}
}
mod checkout_operations {
use super::*;
#[tokio::test]
async fn test_checkout_branch() {
let mut bash = create_git_bash();
setup_repo_with_commit(&mut bash).await;
bash.exec("cd /repo && git branch feature").await.unwrap();
let result = bash.exec("cd /repo && git checkout feature").await.unwrap();
assert_eq!(result.exit_code, 0);
assert!(result.stdout.contains("Switched to branch 'feature'"));
let result = bash.exec("cd /repo && git branch").await.unwrap();
assert!(result.stdout.contains("* feature"));
}
#[tokio::test]
async fn test_checkout_create_branch() {
let mut bash = create_git_bash();
setup_repo_with_commit(&mut bash).await;
let result = bash
.exec("cd /repo && git checkout -b newbranch")
.await
.unwrap();
assert_eq!(result.exit_code, 0);
assert!(result.stdout.contains("Switched to branch 'newbranch'"));
let result = bash.exec("cd /repo && git branch").await.unwrap();
assert!(result.stdout.contains("* newbranch"));
}
#[tokio::test]
async fn test_checkout_nonexistent() {
let mut bash = create_git_bash();
setup_repo_with_commit(&mut bash).await;
let result = bash
.exec("cd /repo && git checkout nonexistent")
.await
.unwrap();
assert_ne!(result.exit_code, 0);
assert!(result.stderr.contains("did not match"));
}
#[tokio::test]
async fn test_checkout_no_args() {
let mut bash = create_git_bash();
setup_repo_with_commit(&mut bash).await;
let result = bash.exec("cd /repo && git checkout").await.unwrap();
assert_ne!(result.exit_code, 0);
assert!(result.stderr.contains("must specify"));
}
#[tokio::test]
async fn test_checkout_commit_hash() {
let mut bash = create_git_bash();
setup_repo_with_commit(&mut bash).await;
let log_result = bash.exec("cd /repo && git log -1").await.unwrap();
let hash = log_result
.stdout
.lines()
.find(|l| l.starts_with("commit"))
.map(|l| l.strip_prefix("commit ").unwrap_or(l).trim())
.unwrap_or("abcd1234");
let result = bash
.exec(&format!("cd /repo && git checkout {}", hash))
.await
.unwrap();
assert_eq!(result.exit_code, 0);
assert!(result.stdout.contains("detached HEAD"));
}
}
mod diff_operations {
use super::*;
#[tokio::test]
async fn test_diff_basic() {
let mut bash = create_git_bash();
setup_repo_with_commit(&mut bash).await;
let result = bash.exec("cd /repo && git diff").await.unwrap();
assert_eq!(result.exit_code, 0);
assert!(result.stdout.contains("Diff output"));
}
#[tokio::test]
async fn test_diff_not_a_repo() {
let mut bash = create_git_bash();
let result = bash.exec("cd / && git diff").await.unwrap();
assert_ne!(result.exit_code, 0);
assert!(result.stderr.contains("not a git repository"));
}
}
mod reset_operations {
use super::*;
#[tokio::test]
async fn test_reset_soft() {
let mut bash = create_git_bash();
setup_repo_with_commit(&mut bash).await;
bash.exec("echo 'new' > /repo/new.txt").await.unwrap();
bash.exec("cd /repo && git add new.txt").await.unwrap();
let result = bash.exec("cd /repo && git reset --soft").await.unwrap();
assert_eq!(result.exit_code, 0);
}
#[tokio::test]
async fn test_reset_mixed() {
let mut bash = create_git_bash();
setup_repo_with_commit(&mut bash).await;
bash.exec("echo 'new' > /repo/new.txt").await.unwrap();
bash.exec("cd /repo && git add new.txt").await.unwrap();
let result = bash.exec("cd /repo && git reset --mixed").await.unwrap();
assert_eq!(result.exit_code, 0);
assert!(result.stdout.contains("Unstaged changes"));
}
#[tokio::test]
async fn test_reset_hard() {
let mut bash = create_git_bash();
setup_repo_with_commit(&mut bash).await;
bash.exec("echo 'new' > /repo/new.txt").await.unwrap();
bash.exec("cd /repo && git add new.txt").await.unwrap();
let result = bash.exec("cd /repo && git reset --hard").await.unwrap();
assert_eq!(result.exit_code, 0);
assert!(result.stdout.contains("HEAD is now at"));
}
#[tokio::test]
async fn test_reset_invalid_mode() {
let mut bash = create_git_bash();
setup_repo_with_commit(&mut bash).await;
let result = bash.exec("cd /repo && git reset --invalid").await.unwrap();
assert_ne!(result.exit_code, 0);
assert!(result.stderr.contains("unknown switch"));
}
#[tokio::test]
async fn test_reset_clears_staging() {
let mut bash = create_git_bash();
setup_repo_with_commit(&mut bash).await;
bash.exec("echo 'new' > /repo/new.txt").await.unwrap();
bash.exec("cd /repo && git add new.txt").await.unwrap();
let result = bash.exec("cd /repo && git status").await.unwrap();
assert!(result.stdout.contains("new.txt"));
assert!(result.stdout.contains("Changes to be committed"));
bash.exec("cd /repo && git reset --mixed").await.unwrap();
let result = bash.exec("cd /repo && git status").await.unwrap();
assert!(result.stdout.contains("Untracked files"));
}
}
mod help_messages {
use super::*;
#[tokio::test]
async fn test_git_help_shows_all_commands() {
let mut bash = create_git_bash();
let result = bash.exec("git").await.unwrap();
assert!(result.stderr.contains("branch"));
assert!(result.stderr.contains("checkout"));
assert!(result.stderr.contains("diff"));
assert!(result.stderr.contains("reset"));
}
}