use crate::stack::sync::{is_droppable, sync_would_mutate, PrMeta};
use std::fs;
mod support;
use support::{commit_file, git, init_repo};
fn meta(state: &str, head_sha: &str) -> PrMeta {
PrMeta {
head_sha: head_sha.to_string(),
head_owner: Some("Automattic".to_string()),
head_name: Some("studio".to_string()),
state: state.to_string(),
title: Some("test PR".to_string()),
url: Some("https://github.com/Automattic/studio/pull/1".to_string()),
review_decision: None,
merged_at: if state == "MERGED" {
Some("2026-04-26T00:00:00Z".to_string())
} else {
None
},
}
}
#[test]
fn is_droppable_drops_merged_pr_with_head_reachable_from_base() {
let (dir, path) = init_repo();
let sha = commit_file(&dir, &path, "a.txt", "a\n", "PR #1 commit on main");
let meta = meta("MERGED", &sha);
assert!(
is_droppable(&meta, &path, "main"),
"merged PR with head SHA in base should be droppable"
);
}
#[test]
fn is_droppable_drops_merged_pr_with_squash_merged_content() {
let (dir, path) = init_repo();
git(&path, &["checkout", "-q", "-b", "pr-feature"]);
let pr_head = commit_file(&dir, &path, "feat.txt", "feature\n", "PR #1 head");
git(&path, &["checkout", "-q", "main"]);
fs::write(dir.path().join("feat.txt"), "feature\n").unwrap();
git(&path, &["add", "."]);
git(&path, &["commit", "-q", "-m", "Squash-merge PR #1"]);
let meta = meta("MERGED", &pr_head);
assert!(
is_droppable(&meta, &path, "main"),
"merged PR with squash-merged content should be droppable via patch_in_base"
);
}
#[test]
fn is_droppable_keeps_open_pr() {
let (dir, path) = init_repo();
let sha = commit_file(&dir, &path, "a.txt", "a\n", "PR commit on main");
let meta = meta("OPEN", &sha);
assert!(
!is_droppable(&meta, &path, "main"),
"OPEN PR must stay in spec regardless of base content"
);
}
#[test]
fn is_droppable_keeps_closed_pr() {
let (dir, path) = init_repo();
let sha = commit_file(&dir, &path, "a.txt", "a\n", "commit");
let meta = meta("CLOSED", &sha);
assert!(
!is_droppable(&meta, &path, "main"),
"CLOSED PR must stay in spec — only MERGED qualifies for auto-drop"
);
}
#[test]
fn is_droppable_keeps_merged_pr_when_content_not_in_base() {
let (dir, path) = init_repo();
git(&path, &["checkout", "-q", "-b", "side"]);
let pr_head = commit_file(&dir, &path, "x.txt", "x\n", "side commit");
git(&path, &["checkout", "-q", "main"]);
let meta = meta("MERGED", &pr_head);
assert!(
!is_droppable(&meta, &path, "main"),
"merged PR whose content isn't in base must stay (rebase-force-push edge case)"
);
}
#[test]
fn is_droppable_keeps_pr_with_unknown_head_sha() {
let (_dir, path) = init_repo();
let meta = meta("MERGED", "deadbeefdeadbeefdeadbeefdeadbeefdeadbeef");
assert!(
!is_droppable(&meta, &path, "main"),
"PR with unfetchable head SHA must stay — no local evidence to drop on"
);
}
#[test]
fn is_droppable_keeps_pr_with_empty_head_sha() {
let (_dir, path) = init_repo();
let meta = meta("MERGED", "");
assert!(
!is_droppable(&meta, &path, "main"),
"PR with empty head SHA must stay"
);
}
#[test]
fn is_droppable_state_check_is_case_sensitive() {
let (dir, path) = init_repo();
let sha = commit_file(&dir, &path, "a.txt", "a\n", "commit");
let mixed_case = meta("Merged", &sha);
assert!(
!is_droppable(&mixed_case, &path, "main"),
"is_droppable must match gh's canonical 'MERGED' exactly"
);
let lower_case = meta("merged", &sha);
assert!(
!is_droppable(&lower_case, &path, "main"),
"is_droppable must match gh's canonical 'MERGED' exactly"
);
}
#[test]
fn sync_would_mutate_false_for_clean_materialized_target() {
assert!(
!sync_would_mutate(true, Some(0), Some(0), 0, 0),
"existing target at base with no drops or replays is a no-op"
);
}
#[test]
fn sync_would_mutate_true_when_target_missing() {
assert!(
sync_would_mutate(false, None, None, 0, 0),
"sync would create the target branch"
);
}
#[test]
fn sync_would_mutate_true_when_target_diverged_from_base() {
assert!(
sync_would_mutate(true, Some(1), Some(0), 0, 0),
"sync would drop target-only commits by recreating target from base"
);
assert!(
sync_would_mutate(true, Some(0), Some(1), 0, 0),
"sync would move target forward to the newer base"
);
}
#[test]
fn sync_would_mutate_true_for_drop_or_replay_plan() {
assert!(
sync_would_mutate(true, Some(0), Some(0), 1, 0),
"sync would mutate the stack spec by dropping merged PRs"
);
assert!(
sync_would_mutate(true, Some(0), Some(0), 0, 1),
"sync would replay PRs onto the rebuilt target"
);
}