use std::process::Command; use tempfile::TempDir; fn arc_cmd() -> Command { let mut cmd = Command::new(env!("CARGO_BIN_EXE_arc")); cmd.env("NO_COLOR", "1"); cmd } fn init_repo() -> TempDir { let dir = TempDir::new().unwrap(); arc_cmd() .arg("init") .current_dir(dir.path()) .output() .expect("failed to init"); dir } fn commit_file(dir: &TempDir, name: &str, content: &str, msg: &str) { std::fs::write(dir.path().join(name), content).unwrap(); let output = arc_cmd() .args(["commit", msg]) .current_dir(dir.path()) .output() .expect("failed to commit"); assert!( output.status.success(), "commit failed: {}", String::from_utf8_lossy(&output.stderr) ); } fn run_ok(dir: &TempDir, args: &[&str]) -> String { let output = arc_cmd() .args(args) .current_dir(dir.path()) .output() .expect("failed to run"); assert!( output.status.success(), "command {:?} failed: {}", args, String::from_utf8_lossy(&output.stderr) ); String::from_utf8_lossy(&output.stdout).trim().to_string() } fn run_fail(dir: &TempDir, args: &[&str]) -> String { let output = arc_cmd() .args(args) .current_dir(dir.path()) .output() .expect("failed to run"); assert!( !output.status.success(), "command {:?} should have failed but succeeded: {}", args, String::from_utf8_lossy(&output.stdout) ); String::from_utf8_lossy(&output.stderr).trim().to_string() } #[test] fn stash_create_creates_stash() { let dir = init_repo(); let stdout = run_ok(&dir, &["stash", "create", "wip"]); assert!(stdout.contains("stash 'wip' created")); let stash_path = dir.path().join(".arc/stashes/named/wip.yml"); assert!(stash_path.exists()); } #[test] fn stash_create_sets_active() { let dir = init_repo(); run_ok(&dir, &["stash", "create", "wip"]); let state = std::fs::read_to_string(dir.path().join(".arc/stashes/state.yml")).unwrap(); assert!(state.contains("wip")); } #[test] fn stash_create_fails_if_exists() { let dir = init_repo(); run_ok(&dir, &["stash", "create", "wip"]); let stderr = run_fail(&dir, &["stash", "create", "wip"]); assert!(stderr.contains("stash already exists")); } #[test] fn stash_create_fails_invalid_name() { let dir = init_repo(); let stderr = run_fail(&dir, &["stash", "create", "../escape"]); assert!(stderr.contains("invalid ref name")); } #[test] fn stash_use_switches_active() { let dir = init_repo(); run_ok(&dir, &["stash", "create", "first"]); run_ok(&dir, &["stash", "create", "second"]); run_ok(&dir, &["stash", "use", "first"]); let state = std::fs::read_to_string(dir.path().join(".arc/stashes/state.yml")).unwrap(); assert!(state.contains("first")); } #[test] fn stash_use_fails_nonexistent() { let dir = init_repo(); let stderr = run_fail(&dir, &["stash", "use", "nope"]); assert!(stderr.contains("stash not found")); } #[test] fn stash_push_saves_and_resets() { let dir = init_repo(); commit_file(&dir, "a.txt", "hello\n", "initial"); run_ok(&dir, &["stash", "create", "wip"]); std::fs::write(dir.path().join("a.txt"), "modified\n").unwrap(); let stdout = run_ok(&dir, &["stash", "push"]); assert!(stdout.contains("pushed")); assert!(stdout.contains("change(s)")); let content = std::fs::read_to_string(dir.path().join("a.txt")).unwrap(); assert_eq!(content, "hello\n"); } #[test] fn stash_push_handles_added_files() { let dir = init_repo(); commit_file(&dir, "a.txt", "hello\n", "initial"); run_ok(&dir, &["stash", "create", "wip"]); std::fs::write(dir.path().join("new.txt"), "added\n").unwrap(); run_ok(&dir, &["stash", "push"]); assert!(!dir.path().join("new.txt").exists()); } #[test] fn stash_push_handles_deleted_files() { let dir = init_repo(); commit_file(&dir, "a.txt", "hello\n", "initial"); run_ok(&dir, &["stash", "create", "wip"]); std::fs::remove_file(dir.path().join("a.txt")).unwrap(); run_ok(&dir, &["stash", "push"]); let content = std::fs::read_to_string(dir.path().join("a.txt")).unwrap(); assert_eq!(content, "hello\n"); } #[test] fn stash_push_fails_no_active() { let dir = init_repo(); commit_file(&dir, "a.txt", "hello\n", "initial"); std::fs::write(dir.path().join("a.txt"), "changed\n").unwrap(); let stderr = run_fail(&dir, &["stash", "push"]); assert!(stderr.contains("no active stash")); } #[test] fn stash_push_fails_clean_worktree() { let dir = init_repo(); commit_file(&dir, "a.txt", "hello\n", "initial"); run_ok(&dir, &["stash", "create", "wip"]); let stderr = run_fail(&dir, &["stash", "push"]); assert!(stderr.contains("nothing to stash")); } #[test] fn stash_pop_restores_changes() { let dir = init_repo(); commit_file(&dir, "a.txt", "hello\n", "initial"); run_ok(&dir, &["stash", "create", "wip"]); std::fs::write(dir.path().join("a.txt"), "modified\n").unwrap(); run_ok(&dir, &["stash", "push"]); let stdout = run_ok(&dir, &["stash", "pop"]); assert!(stdout.contains("popped")); let content = std::fs::read_to_string(dir.path().join("a.txt")).unwrap(); assert_eq!(content, "modified\n"); } #[test] fn stash_pop_restores_added_files() { let dir = init_repo(); commit_file(&dir, "a.txt", "hello\n", "initial"); run_ok(&dir, &["stash", "create", "wip"]); std::fs::write(dir.path().join("new.txt"), "added\n").unwrap(); run_ok(&dir, &["stash", "push"]); assert!(!dir.path().join("new.txt").exists()); run_ok(&dir, &["stash", "pop"]); let content = std::fs::read_to_string(dir.path().join("new.txt")).unwrap(); assert_eq!(content, "added\n"); } #[test] fn stash_pop_restores_deleted_files() { let dir = init_repo(); commit_file(&dir, "a.txt", "hello\n", "initial"); run_ok(&dir, &["stash", "create", "wip"]); std::fs::remove_file(dir.path().join("a.txt")).unwrap(); run_ok(&dir, &["stash", "push"]); run_ok(&dir, &["stash", "pop"]); assert!(!dir.path().join("a.txt").exists()); } #[test] fn stash_pop_fails_no_active() { let dir = init_repo(); let stderr = run_fail(&dir, &["stash", "pop"]); assert!(stderr.contains("no active stash")); } #[test] fn stash_pop_fails_empty_stash() { let dir = init_repo(); run_ok(&dir, &["stash", "create", "wip"]); let stderr = run_fail(&dir, &["stash", "pop"]); assert!(stderr.contains("stash is empty")); } #[test] fn stash_pop_fails_dirty_worktree() { let dir = init_repo(); commit_file(&dir, "a.txt", "hello\n", "initial"); run_ok(&dir, &["stash", "create", "wip"]); std::fs::write(dir.path().join("a.txt"), "modified\n").unwrap(); run_ok(&dir, &["stash", "push"]); std::fs::write(dir.path().join("a.txt"), "dirty\n").unwrap(); let stderr = run_fail(&dir, &["stash", "pop"]); assert!(stderr.contains("uncommitted changes")); } #[test] fn stash_pop_fails_base_mismatch() { let dir = init_repo(); commit_file(&dir, "a.txt", "hello\n", "initial"); run_ok(&dir, &["stash", "create", "wip"]); std::fs::write(dir.path().join("a.txt"), "modified\n").unwrap(); run_ok(&dir, &["stash", "push"]); commit_file(&dir, "b.txt", "new file\n", "second commit"); let stderr = run_fail(&dir, &["stash", "pop"]); assert!(stderr.contains("stash base does not match")); } #[test] fn stash_rm_removes_stash() { let dir = init_repo(); run_ok(&dir, &["stash", "create", "wip"]); let stdout = run_ok(&dir, &["stash", "rm", "wip"]); assert!(stdout.contains("stash 'wip' removed")); let stash_path = dir.path().join(".arc/stashes/named/wip.yml"); assert!(!stash_path.exists()); } #[test] fn stash_rm_clears_active_if_removed() { let dir = init_repo(); run_ok(&dir, &["stash", "create", "wip"]); run_ok(&dir, &["stash", "rm", "wip"]); let state = std::fs::read_to_string(dir.path().join(".arc/stashes/state.yml")).unwrap(); assert!(state.contains("null") || !state.contains("wip")); } #[test] fn stash_rm_fails_nonexistent() { let dir = init_repo(); let stderr = run_fail(&dir, &["stash", "rm", "nope"]); assert!(stderr.contains("stash not found")); } #[test] fn stash_list_shows_stashes() { let dir = init_repo(); run_ok(&dir, &["stash", "create", "alpha"]); run_ok(&dir, &["stash", "create", "beta"]); let stdout = run_ok(&dir, &["stash", "list"]); assert!(stdout.contains("alpha")); assert!(stdout.contains("beta")); } #[test] fn stash_list_marks_active() { let dir = init_repo(); run_ok(&dir, &["stash", "create", "alpha"]); run_ok(&dir, &["stash", "create", "beta"]); let stdout = run_ok(&dir, &["stash", "list"]); assert!(stdout.contains("★ beta")); let has_inactive_alpha = stdout.lines().any(|l| l.trim_start().starts_with("alpha")); assert!(has_inactive_alpha); assert!(!stdout.contains("★ alpha")); } #[test] fn stash_list_sorted() { let dir = init_repo(); run_ok(&dir, &["stash", "create", "zebra"]); run_ok(&dir, &["stash", "create", "alpha"]); let stdout = run_ok(&dir, &["stash", "list"]); let alpha_pos = stdout.find("alpha").unwrap(); let zebra_pos = stdout.find("zebra").unwrap(); assert!(alpha_pos < zebra_pos); } #[test] fn stash_list_shows_entry_count() { let dir = init_repo(); commit_file(&dir, "a.txt", "hello\n", "initial"); run_ok(&dir, &["stash", "create", "wip"]); let stdout = run_ok(&dir, &["stash", "list"]); assert!(stdout.contains("0 entries")); std::fs::write(dir.path().join("a.txt"), "modified\n").unwrap(); run_ok(&dir, &["stash", "push"]); let stdout = run_ok(&dir, &["stash", "list"]); assert!(stdout.contains("1 entries")); } #[test] fn stash_list_empty() { let dir = init_repo(); let stdout = run_ok(&dir, &["stash", "list"]); assert!(stdout.contains("no stashes")); } #[test] fn stash_push_pop_multiple() { let dir = init_repo(); commit_file(&dir, "a.txt", "v1\n", "initial"); run_ok(&dir, &["stash", "create", "wip"]); std::fs::write(dir.path().join("a.txt"), "v2\n").unwrap(); run_ok(&dir, &["stash", "push"]); std::fs::write(dir.path().join("a.txt"), "v3\n").unwrap(); run_ok(&dir, &["stash", "push"]); run_ok(&dir, &["stash", "pop"]); let content = std::fs::read_to_string(dir.path().join("a.txt")).unwrap(); assert_eq!(content, "v3\n"); run_ok(&dir, &["reset"]); run_ok(&dir, &["stash", "pop"]); let content = std::fs::read_to_string(dir.path().join("a.txt")).unwrap(); assert_eq!(content, "v2\n"); } #[test] fn stash_push_on_unborn() { let dir = init_repo(); run_ok(&dir, &["stash", "create", "wip"]); std::fs::write(dir.path().join("new.txt"), "content\n").unwrap(); run_ok(&dir, &["stash", "push"]); assert!(!dir.path().join("new.txt").exists()); run_ok(&dir, &["stash", "pop"]); let content = std::fs::read_to_string(dir.path().join("new.txt")).unwrap(); assert_eq!(content, "content\n"); }