use anyhow::{Context, Result};
use std::path::{Path, PathBuf};
use std::process::Command;
use std::time::{SystemTime, UNIX_EPOCH};
use tempfile::TempDir;
pub struct TestWorkingDir {
original_dir: PathBuf,
}
impl TestWorkingDir {
pub fn new() -> Result<Self> {
let original_dir = match std::env::current_dir() {
Ok(dir) if dir.exists() => dir,
Ok(_dir) => {
let temp_dir = std::env::temp_dir();
std::env::set_current_dir(&temp_dir)
.context("Failed to change to temp directory")?;
temp_dir
}
Err(_) => {
let temp_dir = std::env::temp_dir();
std::env::set_current_dir(&temp_dir)
.context("Failed to change to temp directory")?;
temp_dir
}
};
Ok(TestWorkingDir { original_dir })
}
pub fn change_to(&self, path: &Path) -> Result<()> {
std::env::set_current_dir(path)
.with_context(|| format!("Failed to change directory to {}", path.display()))
}
}
impl Drop for TestWorkingDir {
fn drop(&mut self) {
let _ = std::env::set_current_dir(&self.original_dir);
}
}
pub struct TestGitRepo {
#[allow(dead_code)]
temp_dir: TempDir,
path: PathBuf,
}
impl TestGitRepo {
pub fn new() -> Result<Self> {
let suffix = format!(
"{}-{}",
std::process::id(),
SystemTime::now()
.duration_since(UNIX_EPOCH)
.context("System time before UNIX epoch")?
.as_nanos()
);
let temp_dir = TempDir::with_prefix(format!("prodigy-test-{}", suffix))
.context("Failed to create temporary directory")?;
let path = temp_dir.path().to_path_buf();
let output = Command::new("git")
.current_dir(&path)
.args(["init"])
.output()
.context("Failed to initialize git repository")?;
if !output.status.success() {
let stderr = String::from_utf8_lossy(&output.stderr);
anyhow::bail!("Git init failed: {}", stderr);
}
Command::new("git")
.current_dir(&path)
.args(["config", "user.email", "test@test.com"])
.output()
.context("Failed to configure git user email")?;
Command::new("git")
.current_dir(&path)
.args(["config", "user.name", "Test User"])
.output()
.context("Failed to configure git user name")?;
Ok(TestGitRepo { temp_dir, path })
}
pub fn path(&self) -> &Path {
&self.path
}
pub fn commit(&self, message: &str) -> Result<()> {
let output = Command::new("git")
.current_dir(&self.path)
.args(["commit", "--allow-empty", "-m", message])
.output()
.context("Failed to create git commit")?;
if !output.status.success() {
let stderr = String::from_utf8_lossy(&output.stderr);
anyhow::bail!("Git commit failed: {}", stderr);
}
Ok(())
}
pub fn create_branch(&self, branch_name: &str) -> Result<()> {
let output = Command::new("git")
.current_dir(&self.path)
.args(["checkout", "-b", branch_name])
.output()
.context("Failed to create git branch")?;
if !output.status.success() {
let stderr = String::from_utf8_lossy(&output.stderr);
anyhow::bail!("Git branch creation failed: {}", stderr);
}
Ok(())
}
pub fn checkout(&self, branch_name: &str) -> Result<()> {
let output = Command::new("git")
.current_dir(&self.path)
.args(["checkout", branch_name])
.output()
.context("Failed to checkout git branch")?;
if !output.status.success() {
let stderr = String::from_utf8_lossy(&output.stderr);
anyhow::bail!("Git checkout failed: {}", stderr);
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_working_dir_restore() -> Result<()> {
let valid_dir = std::env::temp_dir();
std::env::set_current_dir(&valid_dir)?;
let wd = TestWorkingDir::new()?;
let original_dir = std::env::current_dir()?.canonicalize()?;
let temp_dir = TempDir::new()?;
wd.change_to(temp_dir.path())?;
assert_eq!(
std::env::current_dir()?.canonicalize()?,
temp_dir.path().canonicalize()?
);
drop(wd);
assert_eq!(std::env::current_dir()?.canonicalize()?, original_dir);
Ok(())
}
#[test]
fn test_git_repo_creation() -> Result<()> {
let repo = TestGitRepo::new()?;
assert!(repo.path().exists());
assert!(repo.path().join(".git").exists());
Ok(())
}
#[test]
fn test_git_repo_commit() -> Result<()> {
let repo = TestGitRepo::new()?;
repo.commit("Test commit")?;
let output = Command::new("git")
.current_dir(repo.path())
.args(["log", "--oneline"])
.output()?;
assert!(output.status.success());
let log = String::from_utf8_lossy(&output.stdout);
assert!(log.contains("Test commit"));
Ok(())
}
}