use super::super::common::TestFixture;
use crate::reducer::boundary::MainEffectHandler;
use crate::reducer::event::PipelineEvent;
use crate::workspace::{MemoryWorkspace, Workspace};
use std::path::PathBuf;
#[test]
fn test_check_commit_diff_emits_prepared_event() {
use crate::reducer::prompt_inputs::sha256_hex_str;
let mut fixture = TestFixture::new();
let ctx = fixture.ctx();
let result = MainEffectHandler::check_commit_diff_with_content(&ctx, "")
.expect("check_commit_diff_with_content should succeed");
assert!(matches!(
result.event,
PipelineEvent::Commit(crate::reducer::event::CommitEvent::DiffPrepared {
empty: true,
content_id_sha256,
}) if content_id_sha256 == sha256_hex_str("")
));
}
#[test]
fn test_check_commit_diff_emits_failed_event_on_error() {
let mut fixture = TestFixture::new();
let ctx = fixture.ctx();
let result =
MainEffectHandler::check_commit_diff_with_result(&ctx, Err(anyhow::anyhow!("diff failed")))
.expect("check_commit_diff_with_result should succeed");
assert!(matches!(
result.event,
PipelineEvent::Commit(crate::reducer::event::CommitEvent::DiffPrepared { .. })
));
}
#[test]
fn test_check_commit_diff_discovers_repo_from_ctx_repo_root_not_process_cwd() {
use std::path::Path;
struct RestoreCwd {
original: PathBuf,
}
impl Drop for RestoreCwd {
fn drop(&mut self) {
let _ = std::env::set_current_dir(&self.original);
}
}
let mut fixture = TestFixture::new();
fixture.repo_root = PathBuf::from(std::env::var("CARGO_MANIFEST_DIR").unwrap());
let _restore = RestoreCwd {
original: std::env::current_dir().unwrap(),
};
std::env::set_current_dir(std::env::temp_dir()).unwrap();
let ctx = fixture.ctx();
let _result = MainEffectHandler::check_commit_diff(&ctx)
.expect("check_commit_diff should succeed when repo_root is set");
let diff = fixture
.workspace
.read(Path::new(".agent/tmp/commit_diff.txt"))
.expect("expected commit diff file to be written");
assert!(
!diff.starts_with("## DIFF UNAVAILABLE - INVESTIGATION REQUIRED"),
"Diff should be computed from ctx.repo_root even when process CWD is elsewhere"
);
}
#[test]
fn test_check_commit_diff_uses_head_baseline_not_start_commit() {
use std::path::Path;
let repo_dir = tempfile::TempDir::new().expect("create temp git repo");
let repo = git2::Repository::init(repo_dir.path()).expect("init git repo");
let sig = git2::Signature::now("test", "test@test.com").expect("signature");
let file_committed = "committed_change_file.txt";
let file_working = "working_change_file.txt";
let abs_committed = repo_dir.path().join(file_committed);
let abs_working = repo_dir.path().join(file_working);
std::fs::write(&abs_committed, "base content\n").expect("write committed file A");
std::fs::write(&abs_working, "base content\n").expect("write working file A");
let mut index = repo.index().expect("open index");
index
.add_path(Path::new(file_committed))
.expect("stage committed file A");
index
.add_path(Path::new(file_working))
.expect("stage working file A");
index.write().expect("write index A");
let tree_a = repo
.find_tree(index.write_tree().expect("write tree A"))
.expect("find tree A");
repo.commit(Some("HEAD"), &sig, &sig, "commit A: initial", &tree_a, &[])
.expect("create commit A");
let committed_marker = "COMMITTED_CHANGE_MUST_NOT_APPEAR_IN_DIFF";
std::fs::write(
&abs_committed,
format!("base content\n{committed_marker}\n"),
)
.expect("write committed file for commit B");
let mut index = repo.index().expect("open index for B");
index
.add_path(Path::new(file_committed))
.expect("stage committed file B");
index.write().expect("write index B");
let tree_b = repo
.find_tree(index.write_tree().expect("write tree B"))
.expect("find tree B");
let parent_a = repo
.head()
.expect("head after A")
.peel_to_commit()
.expect("commit A");
repo.commit(
Some("HEAD"),
&sig,
&sig,
"commit B: committed change",
&tree_b,
&[&parent_a],
)
.expect("create commit B");
let uncommitted_marker = "UNCOMMITTED_CHANGE_MUST_APPEAR_IN_DIFF";
std::fs::write(
&abs_working,
format!("base content\n{uncommitted_marker}\n"),
)
.expect("write uncommitted change to working file");
let workspace = MemoryWorkspace::new_test().with_dir(".agent/tmp");
let mut fixture = TestFixture::with_workspace(workspace);
fixture.repo_root = repo_dir.path().to_path_buf();
let ctx = fixture.ctx();
MainEffectHandler::check_commit_diff(&ctx)
.expect("check_commit_diff should succeed with isolated repo");
let diff = fixture
.workspace
.read(Path::new(".agent/tmp/commit_diff.txt"))
.expect("commit diff file must be written");
assert!(
diff.contains(uncommitted_marker),
"expected uncommitted change marker in commit diff; got: {diff}"
);
assert!(
!diff.contains(committed_marker),
"expected already-committed change to be ABSENT from commit diff (HEAD baseline); got: {diff}"
);
}
#[test]
fn test_fresh_commit_context_after_previous_commit() {
use std::path::Path;
const ITERATION1_UNIQUE_MARKER: &str =
"ITERATION1_UNIQUE_MARKER_MUST_NOT_APPEAR_IN_SECOND_DIFF";
const ITERATION2_UNIQUE_MARKER: &str = "ITERATION2_UNIQUE_MARKER_MUST_APPEAR_IN_SECOND_DIFF";
let repo_dir = tempfile::TempDir::new().expect("create temp git repo");
let repo = git2::Repository::init(repo_dir.path()).expect("init git repo");
let sig = git2::Signature::now("test", "test@test.com").expect("signature");
let file1 = "iteration1_file.txt";
let file2 = "iteration2_file.txt";
let abs_file1 = repo_dir.path().join(file1);
let abs_file2 = repo_dir.path().join(file2);
std::fs::write(&abs_file1, "base content\n").expect("write file1 base");
std::fs::write(&abs_file2, "base content\n").expect("write file2 base");
let mut index = repo.index().expect("open index");
index.add_path(Path::new(file1)).expect("stage file1 base");
index.add_path(Path::new(file2)).expect("stage file2 base");
index.write().expect("write index");
let tree_init = repo
.find_tree(index.write_tree().expect("write tree init"))
.expect("find tree init");
repo.commit(Some("HEAD"), &sig, &sig, "initial commit", &tree_init, &[])
.expect("create initial commit");
std::fs::write(
&abs_file1,
format!("base content\n{ITERATION1_UNIQUE_MARKER}\n"),
)
.expect("write file1 iter1 change");
let workspace1 = crate::workspace::MemoryWorkspace::new_test().with_dir(".agent/tmp");
let mut fixture1 = TestFixture::with_workspace(workspace1);
fixture1.repo_root = repo_dir.path().to_path_buf();
{
let ctx = fixture1.ctx();
MainEffectHandler::check_commit_diff(&ctx).expect("first check_commit_diff should succeed");
}
let diff1 = fixture1
.workspace
.read(Path::new(".agent/tmp/commit_diff.txt"))
.expect("first diff file must be written");
assert!(
diff1.contains(ITERATION1_UNIQUE_MARKER),
"first diff must contain ITERATION1 marker; got: {diff1}"
);
let mut index = repo.index().expect("open index for commit");
index
.add_path(Path::new(file1))
.expect("stage file1 for commit");
index.write().expect("write index for commit");
let tree_iter1 = repo
.find_tree(index.write_tree().expect("write tree iter1"))
.expect("find tree iter1");
let parent = repo
.head()
.expect("head after initial")
.peel_to_commit()
.expect("initial commit object");
repo.commit(
Some("HEAD"),
&sig,
&sig,
"iteration 1 commit",
&tree_iter1,
&[&parent],
)
.expect("create iteration 1 commit");
std::fs::write(
&abs_file2,
format!("base content\n{ITERATION2_UNIQUE_MARKER}\n"),
)
.expect("write file2 iter2 change");
let workspace2 = crate::workspace::MemoryWorkspace::new_test().with_dir(".agent/tmp");
let mut fixture2 = TestFixture::with_workspace(workspace2);
fixture2.repo_root = repo_dir.path().to_path_buf();
{
let ctx = fixture2.ctx();
MainEffectHandler::check_commit_diff(&ctx)
.expect("second check_commit_diff should succeed");
}
let diff2 = fixture2
.workspace
.read(Path::new(".agent/tmp/commit_diff.txt"))
.expect("second diff file must be written");
assert!(
diff2.contains(ITERATION2_UNIQUE_MARKER),
"second diff must contain ITERATION2 marker to prove fresh context; got: {diff2}"
);
assert!(
!diff2.contains(ITERATION1_UNIQUE_MARKER),
"second diff must NOT contain ITERATION1 marker (already committed); got: {diff2}"
);
}