//! L8 integration tests for [`Git2Repository`] against a real temporary repo, //! exercising the local flow end to end: init → status → stage → commit → //! branch/current_branch → log, plus the not-a-repo error path. use std::path::PathBuf; use domain::ports::GitPort; use domain::ports::GitError; use domain::project::ProjectPath; use infrastructure::Git2Repository; use uuid::Uuid; struct TempDir(PathBuf); impl TempDir { fn new() -> Self { let p = std::env::temp_dir().join(format!("idea-l8-git-{}", Uuid::new_v4())); std::fs::create_dir_all(&p).unwrap(); Self(p) } fn root(&self) -> ProjectPath { ProjectPath::new(self.0.to_string_lossy().into_owned()).unwrap() } fn write(&self, name: &str, content: &str) { std::fs::write(self.0.join(name), content).unwrap(); } } impl Drop for TempDir { fn drop(&mut self) { let _ = std::fs::remove_dir_all(&self.0); } } #[tokio::test] async fn init_status_stage_commit_branch_log_flow() { let tmp = TempDir::new(); let root = tmp.root(); let git = Git2Repository::new(); git.init(&root).await.expect("init"); // A new untracked file shows up as not-staged. tmp.write("a.txt", "hello"); let status = git.status(&root).await.unwrap(); let a = status .iter() .find(|s| s.path == "a.txt") .expect("a.txt appears in status"); assert!(!a.staged, "untracked file is not staged"); // Staging flips the staged flag. git.stage(&root, "a.txt").await.unwrap(); let staged = git.status(&root).await.unwrap(); assert!( staged.iter().find(|s| s.path == "a.txt").unwrap().staged, "file is staged after stage()" ); // Commit the index. let commit = git.commit(&root, "first commit").await.unwrap(); assert!(!commit.hash.is_empty()); assert_eq!(commit.summary, "first commit"); // After committing, the tree is clean. assert!( git.status(&root).await.unwrap().is_empty(), "no changes after commit" ); // A current branch exists and is listed among local branches. let current = git.current_branch(&root).await.unwrap(); let current = current.expect("a current branch after the first commit"); let branches = git.branches(&root).await.unwrap(); assert!(branches.contains(¤t), "current branch is listed"); // The log has exactly our commit. let log = git.log(&root, 10).await.unwrap(); assert_eq!(log.len(), 1); assert_eq!(log[0].summary, "first commit"); assert_eq!(log[0].hash, commit.hash); } #[tokio::test] async fn status_on_non_repo_is_not_found() { let tmp = TempDir::new(); let err = Git2Repository::new().status(&tmp.root()).await.unwrap_err(); assert!(matches!(err, GitError::NotFound), "got {err:?}"); } #[tokio::test] async fn unstage_after_first_commit_resets_index() { let tmp = TempDir::new(); let root = tmp.root(); let git = Git2Repository::new(); git.init(&root).await.unwrap(); tmp.write("a.txt", "v1"); git.stage(&root, "a.txt").await.unwrap(); git.commit(&root, "c1").await.unwrap(); // Modify + stage, then unstage → the change is no longer staged. tmp.write("a.txt", "v2"); git.stage(&root, "a.txt").await.unwrap(); assert!(git.status(&root).await.unwrap().iter().any(|s| s.path == "a.txt" && s.staged)); git.unstage(&root, "a.txt").await.unwrap(); let st = git.status(&root).await.unwrap(); let a = st.iter().find(|s| s.path == "a.txt").unwrap(); assert!(!a.staged, "unstaged change is no longer in the index"); }