use super::*;
use crate::prompt::Error;
use crate::template::{GitRunner, RealGit};
use std::cell::RefCell;
use std::io;
use tempfile::TempDir;
#[test]
fn terminal_ref_of_reads_the_frontmatter_field() {
let body =
"---\nfrom: a-b\ndeposited_at: t\nepitaph: final-response\nterminal_ref: abc123\n---\nhi";
assert_eq!(terminal_ref_of(body).as_deref(), Some("abc123"));
}
#[test]
fn terminal_ref_of_is_none_without_frontmatter_or_field() {
assert_eq!(terminal_ref_of("terminal_ref: x\n"), None);
assert_eq!(terminal_ref_of("---\nfrom: a\n---\nterminal_ref: x"), None);
assert_eq!(terminal_ref_of("---\nfrom: user\n---\nhello"), None);
assert_eq!(terminal_ref_of("---\nterminal_ref: \n---\n"), None);
assert_eq!(terminal_ref_of("---\nfrom: a\nunterminated"), None);
}
fn git() -> RealGit {
RealGit::new()
}
fn init_repo() -> TempDir {
let dir = TempDir::new().unwrap();
let wt = dir.path();
let g = git();
g.run(wt, &["init", "-b", "main"]).unwrap();
g.run(wt, &["config", "user.email", "t@t"]).unwrap();
g.run(wt, &["config", "user.name", "t"]).unwrap();
std::fs::write(wt.join("base.txt"), "base\n").unwrap();
g.run(wt, &["add", "-A"]).unwrap();
g.run(wt, &["commit", "-m", "fork"]).unwrap();
dir
}
fn write(wt: &std::path::Path, rel: &str, content: &str) {
let path = wt.join(rel);
std::fs::create_dir_all(path.parent().unwrap()).unwrap();
std::fs::write(path, content).unwrap();
}
fn make_child(wt: &std::path::Path, files: &[(&str, &str)]) -> String {
let g = git();
g.run(wt, &["checkout", "-b", "child"]).unwrap();
for (rel, content) in files {
write(wt, rel, content);
}
g.run(wt, &["add", "-A"]).unwrap();
g.run(wt, &["commit", "-m", "child work"]).unwrap();
let terminal = g.run_capture(wt, &["rev-parse", "HEAD"]).unwrap();
g.run(wt, &["checkout", "main"]).unwrap();
terminal
}
#[test]
fn apply_lands_only_work_products_as_one_commit() {
let dir = init_repo();
let wt = dir.path();
let terminal = make_child(
wt,
&[
("feature.txt", "feature\n"),
("messages/001-x.md", "ctx\n"),
("summary/001.md", "sum\n"),
("skills/s/SKILL.md", "skill\n"),
("goal.md", "child goal\n"),
],
);
apply(wt, "p-child", &terminal, &git()).unwrap();
assert_eq!(
std::fs::read_to_string(wt.join("feature.txt")).unwrap(),
"feature\n"
);
assert!(!wt.join("messages").exists());
assert!(!wt.join("summary").exists());
assert!(!wt.join("skills").exists());
assert!(!wt.join("goal.md").exists());
let subject = git()
.run_capture(wt, &["log", "-1", "--pretty=%s"])
.unwrap();
assert_eq!(subject, "work-product transfer [p-child]");
}
#[test]
fn apply_commits_nothing_when_only_context_changed() {
let dir = init_repo();
let wt = dir.path();
let terminal = make_child(
wt,
&[("messages/001-x.md", "ctx\n"), ("summary/001.md", "s\n")],
);
apply(wt, "p-child", &terminal, &git()).unwrap();
let subject = git()
.run_capture(wt, &["log", "-1", "--pretty=%s"])
.unwrap();
assert_eq!(subject, "fork");
assert!(!wt.join("messages").exists());
}
#[test]
fn apply_declines_loudly_when_the_diff_does_not_apply() {
let dir = init_repo();
let wt = dir.path();
let terminal = make_child(wt, &[("feature.txt", "child version\n")]);
write(wt, "feature.txt", "parent version\n");
git().run(wt, &["add", "-A"]).unwrap();
git().run(wt, &["commit", "-m", "parent diverged"]).unwrap();
apply(wt, "p-child", &terminal, &git()).unwrap();
let subject = git()
.run_capture(wt, &["log", "-1", "--pretty=%s"])
.unwrap();
assert_eq!(subject, "parent diverged");
let marked = git()
.run_capture(wt, &["rev-parse", "refs/lernie/conflicted/p-child"])
.unwrap();
assert_eq!(marked, terminal);
assert_eq!(
std::fs::read_to_string(wt.join("feature.txt")).unwrap(),
"parent version\n"
);
}
#[test]
fn apply_surfaces_a_bad_terminal_ref_as_merge_base_error() {
let dir = init_repo();
let wt = dir.path();
let err = apply(wt, "p-child", "deadbeefdeadbeef", &git()).unwrap_err();
assert!(
matches!(
err,
Error::Git {
op: "transfer merge-base",
..
}
),
"got {err:?}"
);
}
#[derive(Default)]
struct StubGit {
fail_diff: bool,
apply_fails: bool,
fail_commit: bool,
fail_update_ref: bool,
invocations: RefCell<Vec<String>>,
}
impl GitRunner for StubGit {
fn run(&self, _dest: &std::path::Path, args: &[&str]) -> io::Result<()> {
self.invocations.borrow_mut().push(args[0].to_string());
match args[0] {
"diff" => {
if self.fail_diff {
return Err(io::Error::other("diff boom"));
}
let out = args
.iter()
.find_map(|a| a.strip_prefix("--output="))
.expect("diff carries --output");
std::fs::write(out, "diff --git a/f b/f\n").unwrap();
Ok(())
}
"apply" if self.apply_fails => Err(io::Error::other("apply boom")),
"apply" => Ok(()),
"commit" if self.fail_commit => Err(io::Error::other("commit boom")),
"update-ref" if self.fail_update_ref => Err(io::Error::other("ref boom")),
_ => Ok(()),
}
}
fn run_capture(&self, _dest: &std::path::Path, args: &[&str]) -> io::Result<String> {
self.invocations.borrow_mut().push(args[0].to_string());
Ok("f0f0f0f0".to_string())
}
}
fn wt() -> TempDir {
TempDir::new().unwrap()
}
#[test]
fn apply_surfaces_diff_failure() {
let d = wt();
let git = StubGit {
fail_diff: true,
..Default::default()
};
let err = apply(d.path(), "c", "term", &git).unwrap_err();
assert!(
matches!(
err,
Error::Git {
op: "transfer diff",
..
}
),
"got {err:?}"
);
}
#[test]
fn apply_surfaces_commit_failure() {
let d = wt();
let git = StubGit {
fail_commit: true,
..Default::default()
};
let err = apply(d.path(), "c", "term", &git).unwrap_err();
assert!(
matches!(
err,
Error::Git {
op: "transfer commit",
..
}
),
"got {err:?}"
);
}
#[test]
fn apply_declines_and_surfaces_update_ref_failure() {
let d = wt();
let git = StubGit {
apply_fails: true,
fail_update_ref: true,
..Default::default()
};
let err = apply(d.path(), "c", "term", &git).unwrap_err();
assert!(
matches!(
err,
Error::Git {
op: "transfer decline update-ref",
..
}
),
"got {err:?}"
);
assert!(git.invocations.borrow().iter().any(|c| c == "update-ref"));
}