185 lines
5.4 KiB
Rust
185 lines
5.4 KiB
Rust
//! Integration tests for the git module.
|
|
//!
|
|
//! These tests create real temporary git repos and exercise GitRunner
|
|
//! and compute_git_state against them. Marked `#[ignore]` by default
|
|
//! since they require `git` to be installed.
|
|
|
|
use dirigent_projects::git::{compute_git_state, GitRunner};
|
|
use std::path::Path;
|
|
use tokio::process::Command;
|
|
|
|
/// Helper: initialize a git repo in the given directory with an initial commit.
|
|
async fn init_repo(dir: &Path) {
|
|
run(dir, &["git", "init"]).await;
|
|
run(dir, &["git", "config", "user.email", "test@test.com"]).await;
|
|
run(dir, &["git", "config", "user.name", "Test"]).await;
|
|
// Create an initial commit so HEAD exists
|
|
let file = dir.join("README.md");
|
|
tokio::fs::write(&file, "# Test\n").await.unwrap();
|
|
run(dir, &["git", "add", "."]).await;
|
|
run(dir, &["git", "commit", "-m", "Initial commit"]).await;
|
|
}
|
|
|
|
async fn run(dir: &Path, args: &[&str]) {
|
|
let status = Command::new(args[0])
|
|
.args(&args[1..])
|
|
.current_dir(dir)
|
|
.output()
|
|
.await
|
|
.unwrap_or_else(|e| panic!("Failed to run {:?}: {e}", args));
|
|
assert!(
|
|
status.status.success(),
|
|
"{:?} failed: {}",
|
|
args,
|
|
String::from_utf8_lossy(&status.stderr)
|
|
);
|
|
}
|
|
|
|
#[tokio::test]
|
|
#[ignore = "requires git"]
|
|
async fn test_current_branch() {
|
|
let dir = tempfile::tempdir().unwrap();
|
|
init_repo(dir.path()).await;
|
|
|
|
let runner = GitRunner::new(dir.path());
|
|
let branch = runner.current_branch().await.unwrap();
|
|
// Default branch may be "main" or "master" depending on git config
|
|
assert!(
|
|
branch == "main" || branch == "master",
|
|
"unexpected branch: {branch}"
|
|
);
|
|
}
|
|
|
|
#[tokio::test]
|
|
#[ignore = "requires git"]
|
|
async fn test_status_clean() {
|
|
let dir = tempfile::tempdir().unwrap();
|
|
init_repo(dir.path()).await;
|
|
|
|
let runner = GitRunner::new(dir.path());
|
|
let status = runner.status().await.unwrap();
|
|
assert!(!status.is_dirty);
|
|
assert_eq!(status.ahead, 0);
|
|
assert_eq!(status.behind, 0);
|
|
}
|
|
|
|
#[tokio::test]
|
|
#[ignore = "requires git"]
|
|
async fn test_status_dirty() {
|
|
let dir = tempfile::tempdir().unwrap();
|
|
init_repo(dir.path()).await;
|
|
|
|
// Create an untracked file
|
|
tokio::fs::write(dir.path().join("dirty.txt"), "dirty")
|
|
.await
|
|
.unwrap();
|
|
|
|
let runner = GitRunner::new(dir.path());
|
|
let status = runner.status().await.unwrap();
|
|
assert!(status.is_dirty);
|
|
}
|
|
|
|
#[tokio::test]
|
|
#[ignore = "requires git"]
|
|
async fn test_remotes_empty() {
|
|
let dir = tempfile::tempdir().unwrap();
|
|
init_repo(dir.path()).await;
|
|
|
|
let runner = GitRunner::new(dir.path());
|
|
let remotes = runner.remotes().await.unwrap();
|
|
assert!(remotes.is_empty());
|
|
}
|
|
|
|
#[tokio::test]
|
|
#[ignore = "requires git"]
|
|
async fn test_worktree_list_single() {
|
|
let dir = tempfile::tempdir().unwrap();
|
|
init_repo(dir.path()).await;
|
|
|
|
let runner = GitRunner::new(dir.path());
|
|
let worktrees = runner.worktree_list().await.unwrap();
|
|
// A non-bare repo always has at least the main worktree
|
|
assert_eq!(worktrees.len(), 1);
|
|
assert!(worktrees[0].branch.is_some());
|
|
}
|
|
|
|
#[tokio::test]
|
|
#[ignore = "requires git"]
|
|
async fn test_worktree_add_and_list() {
|
|
let dir = tempfile::tempdir().unwrap();
|
|
init_repo(dir.path()).await;
|
|
|
|
let wt_path = dir.path().join("wt-feature");
|
|
// Create a branch first
|
|
run(dir.path(), &["git", "branch", "feature"]).await;
|
|
|
|
let runner = GitRunner::new(dir.path());
|
|
runner.worktree_add(&wt_path, "feature").await.unwrap();
|
|
|
|
let worktrees = runner.worktree_list().await.unwrap();
|
|
assert_eq!(worktrees.len(), 2);
|
|
|
|
// Find the feature worktree by branch name (paths may differ due to symlink canonicalization)
|
|
let feature_wt = worktrees
|
|
.iter()
|
|
.find(|w| w.branch.as_deref() == Some("feature"))
|
|
.expect("should find worktree with branch 'feature'");
|
|
assert!(!feature_wt.is_detached);
|
|
}
|
|
|
|
#[tokio::test]
|
|
#[ignore = "requires git"]
|
|
async fn test_compute_git_state() {
|
|
let dir = tempfile::tempdir().unwrap();
|
|
init_repo(dir.path()).await;
|
|
|
|
// Make it dirty
|
|
tokio::fs::write(dir.path().join("new.txt"), "content")
|
|
.await
|
|
.unwrap();
|
|
|
|
let runner = GitRunner::new(dir.path());
|
|
let state = compute_git_state(&runner).await;
|
|
|
|
assert!(!state.branch.is_empty());
|
|
assert!(state.is_dirty);
|
|
assert!(
|
|
state.unexpected.is_empty(),
|
|
"unexpected warnings: {:?}",
|
|
state.unexpected
|
|
);
|
|
// Should have at least the main worktree
|
|
assert!(!state.worktrees.is_empty());
|
|
}
|
|
|
|
#[tokio::test]
|
|
#[ignore = "requires git"]
|
|
async fn test_graceful_degradation_not_a_repo() {
|
|
let dir = tempfile::tempdir().unwrap();
|
|
// Don't init — not a git repo
|
|
|
|
let runner = GitRunner::new(dir.path());
|
|
let state = compute_git_state(&runner).await;
|
|
|
|
// Should have warnings, not panic
|
|
assert!(!state.unexpected.is_empty());
|
|
}
|
|
|
|
#[tokio::test]
|
|
#[ignore = "requires git"]
|
|
async fn test_commit_returns_hash() {
|
|
let dir = tempfile::tempdir().unwrap();
|
|
init_repo(dir.path()).await;
|
|
|
|
let file = dir.path().join("commit_test.txt");
|
|
tokio::fs::write(&file, "data").await.unwrap();
|
|
run(dir.path(), &["git", "add", "."]).await;
|
|
|
|
let runner = GitRunner::new(dir.path());
|
|
let hash = runner.commit("test commit").await.unwrap();
|
|
|
|
// SHA-1 hash is 40 hex chars
|
|
assert_eq!(hash.len(), 40, "unexpected hash: {hash}");
|
|
assert!(hash.chars().all(|c| c.is_ascii_hexdigit()));
|
|
}
|