use super::*;
use std::cell::RefCell;
struct Script {
fail_run: Option<&'static str>,
fail_capture: Option<&'static str>,
rebase_fails: RefCell<u32>,
ls_files: &'static str,
log: &'static str,
rev_parse: &'static str,
providers: &'static str,
}
impl Default for Script {
fn default() -> Self {
Self {
fail_run: None,
fail_capture: None,
rebase_fails: RefCell::new(0),
ls_files: "",
log: "dsha",
rev_parse: "gsha",
providers: "roles:\n worker:\n provider: p\n model: m\n",
}
}
}
impl Script {
fn land(&self) -> Result<Option<Outcome>, Error> {
super::super::land(Path::new("/ws"), "a", Path::new("/ws/agents/a"), self)
}
}
impl crate::template::GitRunner for Script {
fn run(&self, _d: &Path, args: &[&str]) -> std::io::Result<()> {
let joined = args.join(" ");
if let Some(pat) = self.fail_run
&& joined.contains(pat)
{
return Err(std::io::Error::other("stub fail"));
}
if joined.starts_with("worktree add") {
std::fs::create_dir_all(args[args.len() - 2]).unwrap();
}
if joined.contains("rebase") && !joined.contains("--abort") {
let mut left = self.rebase_fails.borrow_mut();
if *left > 0 {
*left -= 1;
return Err(std::io::Error::other("rebase stop"));
}
}
Ok(())
}
fn run_capture(&self, _d: &Path, args: &[&str]) -> std::io::Result<String> {
let joined = args.join(" ");
if let Some(pat) = self.fail_capture
&& joined.contains(pat)
{
return Err(std::io::Error::other("stub fail"));
}
Ok(match args.first().copied() {
Some("rev-parse") if joined.contains("retarget") => "tsha".into(),
Some("rev-parse") => self.rev_parse.into(),
Some("log") => self.log.into(),
Some("merge-base") => self.rev_parse.into(),
Some("for-each-ref") if joined.contains(":short") => "config/default\n".into(),
Some("for-each-ref") => "refs/heads/config/default\n".into(),
Some("show") => self.providers.into(),
Some("write-tree") => "wsha".into(),
Some("commit-tree") => "bsha".into(),
Some("ls-files") => self.ls_files.into(),
_ => "1".into(),
})
}
}
fn assert_op(err: Error, want: &str) {
match err {
Error::Git { op, .. } => assert_eq!(op, want),
other => panic!("{other:?}"),
}
}
#[test]
fn a_clean_scripted_landing_lands() {
assert_eq!(Script::default().land().unwrap(), Some(Outcome::Landed));
}
#[test]
fn an_unreadable_mark_lands_nothing() {
let s = Script {
fail_capture: Some("retarget"),
..Script::default()
};
assert_eq!(s.land().unwrap(), None);
}
#[test]
fn a_governing_config_derivation_failure_surfaces() {
let s = Script {
fail_capture: Some("merge-base"),
..Script::default()
};
assert_op(s.land().unwrap_err(), "retarget governing config");
}
#[test]
fn a_missing_dispatch_commit_is_declined() {
let s = Script {
log: "",
..Script::default()
};
assert_op(s.land().unwrap_err(), "retarget dispatch commit");
}
#[test]
fn a_clear_failure_surfaces_after_the_landing() {
let s = Script {
fail_run: Some("update-ref -d"),
..Script::default()
};
assert_op(s.land().unwrap_err(), "retarget clear mark");
}
#[test]
fn the_role_derivation_and_grant_read_failures_surface() {
let s = Script {
fail_capture: Some("--format=%s -E"),
..Script::default()
};
assert!(s.land().is_err());
let s = Script {
fail_capture: Some("providers.yaml"),
..Script::default()
};
assert!(matches!(s.land().unwrap_err(), Error::ControlRead { .. }));
}
#[test]
fn a_soul_the_target_config_does_not_carry_surfaces() {
let s = Script {
fail_capture: Some("souls/"),
..Script::default()
};
assert!(matches!(s.land().unwrap_err(), Error::ControlRead { .. }));
}
#[test]
fn a_malformed_target_providers_yaml_surfaces() {
let s = Script {
providers: "roles: [not, a, map]\n",
..Script::default()
};
assert!(matches!(s.land().unwrap_err(), Error::Config(_)));
}
#[test]
fn scratch_worktree_and_mint_failures_surface() {
for (pat, op) in [
("worktree add", "retarget scratch worktree"),
("add -A", "retarget add"),
] {
let s = Script {
fail_run: Some(pat),
..Script::default()
};
assert_op(s.land().unwrap_err(), op);
}
for (pat, op) in [
("write-tree", "retarget write-tree"),
("commit-tree", "retarget commit-tree"),
] {
let s = Script {
fail_capture: Some(pat),
..Script::default()
};
assert_op(s.land().unwrap_err(), op);
}
}
#[test]
fn a_dispatch_subject_read_failure_surfaces() {
let s = Script {
fail_capture: Some("--format=%s dsha"),
..Script::default()
};
assert_op(s.land().unwrap_err(), "retarget dispatch subject");
}
const BOTH_SIDES: &str = "100644 bbb 2\tsummary/001.md\n100644 ccc 3\tsummary/001.md\n";
#[test]
fn a_content_conflict_declines_with_the_paths_and_marks_the_branch() {
let s = Script {
rebase_fails: RefCell::new(1),
ls_files: BOTH_SIDES,
..Script::default()
};
assert_eq!(
s.land().unwrap(),
Some(Outcome::Conflicted(vec!["summary/001.md".to_string()])),
);
}
mod preflighting {
use super::*;
use crate::prompt::retarget::preflight;
fn shell() -> tempfile::TempDir {
let d = tempfile::TempDir::new().unwrap();
std::fs::create_dir_all(d.path().join("repo.git")).unwrap();
d
}
#[test]
fn a_lineage_that_will_not_resolve_to_a_commit_surfaces() {
let d = shell();
let s = Script {
fail_capture: Some("^{commit}"),
..Script::default()
};
assert_op(
preflight(d.path(), "a", "default", &s).unwrap_err(),
"retarget resolve target",
);
}
#[test]
fn a_target_that_already_governs_resolves_to_none() {
let d = shell();
assert_eq!(
preflight(d.path(), "a", "default", &Script::default()).unwrap(),
None,
);
}
}