mod common;
use common::TempGitRepo;
#[test]
fn test_review_creates_branch() {
let repo = TempGitRepo::new();
repo.create_branch("develop");
repo.write_file("feature.txt", "new feature");
repo.git(&["add", "."]);
repo.commit("Add feature");
repo.git(&["push", "-u", "origin", "develop"]);
repo.switch_branch("main");
let output = repo.run_cresca(&["review", "main", "develop"]);
assert!(
output.status.success(),
"cresca review should succeed\nstdout: {}\nstderr: {}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
);
let current = repo.current_branch();
assert_eq!(current, "review-main-develop");
}
#[test]
fn test_review_shows_diff() {
let repo = TempGitRepo::new();
repo.create_branch("develop");
repo.write_file("feature.txt", "new feature content");
repo.git(&["add", "."]);
repo.commit("Add feature");
repo.git(&["push", "-u", "origin", "develop"]);
repo.switch_branch("main");
repo.run_cresca(&["review", "main", "develop"]);
assert!(
repo.has_uncommitted_changes(),
"Should have uncommitted changes"
);
let status = repo.git(&["status", "--porcelain"]);
let status_str = String::from_utf8_lossy(&status.stdout);
assert!(
status_str.contains("feature.txt"),
"feature.txt should appear in status"
);
}
#[test]
fn test_approve_commits_staged() {
let repo = TempGitRepo::new();
repo.create_branch("develop");
repo.write_file("reviewed.txt", "reviewed content");
repo.write_file("not_reviewed.txt", "not reviewed content");
repo.git(&["add", "."]);
repo.commit("Add features");
repo.git(&["push", "-u", "origin", "develop"]);
repo.switch_branch("main");
repo.run_cresca(&["review", "main", "develop"]);
repo.git(&["add", "reviewed.txt"]);
let output = repo.run_cresca(&["approve"]);
assert!(
output.status.success(),
"cresca approve should succeed\nstdout: {}\nstderr: {}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
);
let files_in_head = repo.git(&["ls-tree", "--name-only", "HEAD"]);
let files_str = String::from_utf8_lossy(&files_in_head.stdout);
assert!(
files_str.contains("reviewed.txt"),
"reviewed.txt should be committed"
);
let not_reviewed_path = repo.path().join("not_reviewed.txt");
assert!(
!not_reviewed_path.exists(),
"not_reviewed.txt should be discarded"
);
assert!(
!repo.has_uncommitted_changes(),
"Working directory should be clean after approve"
);
}
#[test]
fn test_approve_on_non_review_branch() {
let repo = TempGitRepo::new();
let output = repo.run_cresca(&["approve"]);
assert!(
!output.status.success(),
"cresca approve should fail on non-review branch"
);
let stderr = String::from_utf8_lossy(&output.stderr);
assert!(
stderr.contains("error") || stderr.contains("Not on a review branch"),
"Should show error message about not being on review branch"
);
}
#[test]
fn test_review_with_uncommitted_changes() {
let repo = TempGitRepo::new();
repo.create_branch("develop");
repo.git(&["push", "-u", "origin", "develop"]);
repo.switch_branch("main");
repo.write_file("uncommitted.txt", "uncommitted content");
let output = repo.run_cresca(&["review", "main", "develop"]);
assert!(
!output.status.success(),
"cresca review should fail with uncommitted changes"
);
let stderr = String::from_utf8_lossy(&output.stderr);
assert!(
stderr.contains("error") || stderr.contains("Uncommitted"),
"Should show error about uncommitted changes"
);
}
#[test]
fn test_review_updates_existing_branch() {
let repo = TempGitRepo::new();
repo.create_branch("develop");
repo.write_file("file1.txt", "content 1");
repo.git(&["add", "."]);
repo.commit("Add file1");
repo.git(&["push", "-u", "origin", "develop"]);
repo.switch_branch("main");
repo.run_cresca(&["review", "main", "develop"]);
repo.git(&["add", "."]);
repo.run_cresca(&["approve"]);
repo.switch_branch("develop");
repo.write_file("file2.txt", "content 2");
repo.git(&["add", "."]);
repo.commit("Add file2");
repo.git(&["push", "origin", "develop"]);
repo.switch_branch("review-main-develop");
repo.run_cresca(&["review", "main", "develop"]);
assert!(
repo.path().join("file1.txt").exists(),
"file1.txt should exist from previous approval"
);
let status = repo.git(&["status", "--porcelain"]);
let status_str = String::from_utf8_lossy(&status.stdout);
assert!(
status_str.contains("file2.txt"),
"file2.txt should appear as new unreviewed change"
);
}
#[test]
fn test_review_with_skip_to_option() {
let repo = TempGitRepo::new();
repo.create_branch("develop");
repo.write_file("file1.txt", "content 1");
repo.git(&["add", "."]);
repo.commit("Add file1");
repo.write_file("file2.txt", "content 2");
repo.git(&["add", "."]);
repo.commit("Add file2");
repo.write_file("file3.txt", "content 3");
repo.git(&["add", "."]);
repo.commit("Add file3");
repo.git(&["push", "-u", "origin", "develop"]);
let log_output = repo.git(&["log", "--oneline", "main..develop"]);
let log_str = String::from_utf8_lossy(&log_output.stdout);
let commits: Vec<&str> = log_str.lines().collect();
let file2_hash = commits[1].split_whitespace().next().unwrap();
repo.switch_branch("main");
let output = repo.run_cresca(&["review", "main", "develop", "--skip-to", file2_hash]);
assert!(
output.status.success(),
"cresca review --skip-to should succeed\nstdout: {}\nstderr: {}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
);
let files_in_head = repo.git(&["ls-tree", "--name-only", "HEAD"]);
let files_str = String::from_utf8_lossy(&files_in_head.stdout);
assert!(
files_str.contains("file1.txt"),
"file1.txt should be auto-approved and committed"
);
let status = repo.git(&["status", "--porcelain"]);
let status_str = String::from_utf8_lossy(&status.stdout);
assert!(
status_str.contains("file2.txt"),
"file2.txt should be an unstaged change"
);
assert!(
status_str.contains("file3.txt"),
"file3.txt should be an unstaged change"
);
}
#[test]
fn test_review_with_skip_to_already_approved() {
let repo = TempGitRepo::new();
repo.create_branch("develop");
repo.write_file("file1.txt", "content 1");
repo.git(&["add", "."]);
repo.commit("Add file1");
repo.write_file("file2.txt", "content 2");
repo.git(&["add", "."]);
repo.commit("Add file2");
repo.git(&["push", "-u", "origin", "develop"]);
let log_output = repo.git(&["log", "--oneline", "main..develop"]);
let log_str = String::from_utf8_lossy(&log_output.stdout);
let commits: Vec<&str> = log_str.lines().collect();
let file2_hash = commits[0].split_whitespace().next().unwrap();
let file1_hash = commits[1].split_whitespace().next().unwrap();
repo.switch_branch("main");
repo.run_cresca(&["review", "main", "develop", "--skip-to", file2_hash]);
repo.git(&["add", "."]);
repo.run_cresca(&["approve"]);
let output = repo.run_cresca(&["review", "main", "develop", "--skip-to", file1_hash]);
assert!(
output.status.success(),
"cresca review --skip-to with already approved commits should succeed\nstdout: {}\nstderr: {}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
);
}
#[test]
fn test_status_shows_diff_stats() {
let repo = TempGitRepo::new();
repo.create_branch("develop");
repo.write_file("feature1.txt", "new feature 1");
repo.write_file("feature2.txt", "new feature 2");
repo.git(&["add", "."]);
repo.commit("Add features");
repo.git(&["push", "-u", "origin", "develop"]);
repo.switch_branch("main");
repo.run_cresca(&["review", "main", "develop"]);
let output = repo.run_cresca(&["status"]);
assert!(
output.status.success(),
"cresca status should succeed\nstdout: {}\nstderr: {}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
);
let stdout = String::from_utf8_lossy(&output.stdout);
assert!(
stdout.contains("Review status"),
"Should show review status header"
);
assert!(
stdout.contains("Remaining diff to develop"),
"Should mention develop branch"
);
assert!(stdout.contains("2 file(s)"), "Should show 2 files changed");
assert!(stdout.contains("feature1.txt"), "Should list feature1.txt");
assert!(stdout.contains("feature2.txt"), "Should list feature2.txt");
}
#[test]
fn test_status_on_non_review_branch() {
let repo = TempGitRepo::new();
let output = repo.run_cresca(&["status"]);
assert!(
!output.status.success(),
"cresca status should fail on non-review branch"
);
let stderr = String::from_utf8_lossy(&output.stderr);
assert!(
stderr.contains("error") || stderr.contains("Not on a review branch"),
"Should show error message about not being on review branch"
);
}
#[test]
fn test_status_after_partial_approval() {
let repo = TempGitRepo::new();
repo.create_branch("develop");
repo.write_file("file1.txt", "content 1");
repo.write_file("file2.txt", "content 2");
repo.write_file("file3.txt", "content 3");
repo.git(&["add", "."]);
repo.commit("Add three files");
repo.git(&["push", "-u", "origin", "develop"]);
repo.switch_branch("main");
repo.run_cresca(&["review", "main", "develop"]);
let output = repo.run_cresca(&["status"]);
let stdout = String::from_utf8_lossy(&output.stdout);
assert!(
stdout.contains("3 file(s)"),
"Should initially show 3 files, got: {}",
stdout
);
repo.git(&["add", "file1.txt"]);
repo.run_cresca(&["approve"]);
repo.run_cresca(&["review", "main", "develop"]);
let output = repo.run_cresca(&["status"]);
let stdout = String::from_utf8_lossy(&output.stdout);
assert!(
stdout.contains("file2.txt"),
"file2.txt should be in remaining files, got: {}",
stdout
);
assert!(
stdout.contains("file3.txt"),
"file3.txt should be in remaining files, got: {}",
stdout
);
}
#[test]
fn test_review_with_stop_at_option() {
let repo = TempGitRepo::new();
repo.create_branch("develop");
repo.write_file("file1.txt", "content 1");
repo.git(&["add", "."]);
repo.commit("Add file1");
repo.write_file("file2.txt", "content 2");
repo.git(&["add", "."]);
repo.commit("Add file2");
repo.write_file("file3.txt", "content 3");
repo.git(&["add", "."]);
repo.commit("Add file3");
repo.git(&["push", "-u", "origin", "develop"]);
let log_output = repo.git(&["log", "--oneline", "main..develop"]);
let log_str = String::from_utf8_lossy(&log_output.stdout);
let commits: Vec<&str> = log_str.lines().collect();
let file2_hash = commits[1].split_whitespace().next().unwrap();
repo.switch_branch("main");
let output = repo.run_cresca(&["review", "main", "develop", "--stop-at", file2_hash]);
assert!(
output.status.success(),
"cresca review --stop-at should succeed\nstdout: {}\nstderr: {}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
);
let status = repo.git(&["status", "--porcelain"]);
let status_str = String::from_utf8_lossy(&status.stdout);
assert!(
status_str.contains("file1.txt"),
"file1.txt should be an unstaged change"
);
assert!(
status_str.contains("file2.txt"),
"file2.txt should be an unstaged change"
);
assert!(
!status_str.contains("file3.txt"),
"file3.txt should NOT appear (excluded by --stop-at)"
);
}
#[test]
fn test_review_with_skip_to_and_stop_at() {
let repo = TempGitRepo::new();
repo.create_branch("develop");
repo.write_file("fileA.txt", "content A");
repo.git(&["add", "."]);
repo.commit("Add fileA");
repo.write_file("fileB.txt", "content B");
repo.git(&["add", "."]);
repo.commit("Add fileB");
repo.write_file("fileC.txt", "content C");
repo.git(&["add", "."]);
repo.commit("Add fileC");
repo.write_file("fileD.txt", "content D");
repo.git(&["add", "."]);
repo.commit("Add fileD");
repo.git(&["push", "-u", "origin", "develop"]);
let log_output = repo.git(&["log", "--oneline", "main..develop"]);
let log_str = String::from_utf8_lossy(&log_output.stdout);
let commits: Vec<&str> = log_str.lines().collect();
let _file_d_hash = commits[0].split_whitespace().next().unwrap();
let file_c_hash = commits[1].split_whitespace().next().unwrap();
let file_b_hash = commits[2].split_whitespace().next().unwrap();
let _file_a_hash = commits[3].split_whitespace().next().unwrap();
repo.switch_branch("main");
let output = repo.run_cresca(&[
"review",
"main",
"develop",
"--skip-to",
file_b_hash,
"--stop-at",
file_c_hash,
]);
assert!(
output.status.success(),
"cresca review --skip-to --stop-at should succeed\nstdout: {}\nstderr: {}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
);
let files_in_head = repo.git(&["ls-tree", "--name-only", "HEAD"]);
let files_str = String::from_utf8_lossy(&files_in_head.stdout);
assert!(
files_str.contains("fileA.txt"),
"fileA.txt should be auto-approved and committed"
);
let status = repo.git(&["status", "--porcelain"]);
let status_str = String::from_utf8_lossy(&status.stdout);
assert!(
status_str.contains("fileB.txt"),
"fileB.txt should be an unstaged change"
);
assert!(
status_str.contains("fileC.txt"),
"fileC.txt should be an unstaged change"
);
assert!(
!status_str.contains("fileD.txt"),
"fileD.txt should NOT appear (excluded by --stop-at), got: {}",
status_str
);
assert!(
!files_str.contains("fileD.txt"),
"fileD.txt should not be in HEAD"
);
}
#[test]
fn test_review_with_invalid_stop_at() {
let repo = TempGitRepo::new();
repo.create_branch("develop");
repo.write_file("file1.txt", "content 1");
repo.git(&["add", "."]);
repo.commit("Add file1");
repo.git(&["push", "-u", "origin", "develop"]);
repo.switch_branch("main");
let output = repo.run_cresca(&["review", "main", "develop", "--stop-at", "invalidhash"]);
assert!(
!output.status.success(),
"cresca review --stop-at with invalid hash should fail"
);
let stderr = String::from_utf8_lossy(&output.stderr);
assert!(
stderr.contains("error") && stderr.contains("invalidhash"),
"Should show error about invalid commit hash, got: {}",
stderr
);
}
#[test]
fn test_review_with_stop_at_before_skip_to() {
let repo = TempGitRepo::new();
repo.create_branch("develop");
repo.write_file("file1.txt", "content 1");
repo.git(&["add", "."]);
repo.commit("Add file1");
repo.write_file("file2.txt", "content 2");
repo.git(&["add", "."]);
repo.commit("Add file2");
repo.write_file("file3.txt", "content 3");
repo.git(&["add", "."]);
repo.commit("Add file3");
repo.git(&["push", "-u", "origin", "develop"]);
let log_output = repo.git(&["log", "--oneline", "main..develop"]);
let log_str = String::from_utf8_lossy(&log_output.stdout);
let commits: Vec<&str> = log_str.lines().collect();
let file3_hash = commits[0].split_whitespace().next().unwrap();
let file1_hash = commits[2].split_whitespace().next().unwrap();
repo.switch_branch("main");
let output = repo.run_cresca(&[
"review",
"main",
"develop",
"--skip-to",
file3_hash,
"--stop-at",
file1_hash,
]);
assert!(
!output.status.success(),
"cresca review should fail when --stop-at is before --skip-to"
);
let stderr = String::from_utf8_lossy(&output.stderr);
assert!(
stderr.contains("error") && stderr.contains("--stop-at"),
"Should show error about --stop-at being before --skip-to, got: {}",
stderr
);
}