use super::super::deposit::{Epitaph, deposit_result, returned_ref};
use super::super::{inbox_dir, parent_of};
use crate::prompt::Clock;
use crate::template::GitRunner;
use std::cell::RefCell;
use std::io;
use std::path::Path;
use tempfile::TempDir;
#[derive(Default)]
struct RecGit {
runs: RefCell<Vec<Vec<String>>>,
fail: bool,
name: &'static str,
}
impl GitRunner for RecGit {
fn run(&self, _dest: &Path, args: &[&str]) -> io::Result<()> {
self.runs
.borrow_mut()
.push(args.iter().map(|a| a.to_string()).collect());
if self.fail {
return Err(io::Error::other("update-ref refused"));
}
Ok(())
}
fn run_capture(&self, _dest: &Path, _args: &[&str]) -> io::Result<String> {
Ok(self.name.to_string())
}
}
struct FixedClock;
impl Clock for FixedClock {
fn now_iso8601(&self) -> String {
"2026-07-11T00:00:00Z".into()
}
fn now_compact(&self) -> String {
unreachable!("result deposit never reads the compact clock")
}
}
fn read(path: &Path) -> String {
std::fs::read_to_string(path).unwrap()
}
#[test]
fn epitaph_spellings_are_hyphenated() {
assert_eq!(Epitaph::FinalResponse.as_str(), "final-response");
assert_eq!(Epitaph::Stopped.as_str(), "stopped");
assert_eq!(Epitaph::BudgetExhausted.as_str(), "budget-exhausted");
assert_eq!(Epitaph::Died.as_str(), "died");
}
#[test]
fn result_message_carries_epitaph_ref_and_body_when_spoke() {
let ws = TempDir::new().unwrap();
let path = deposit_result(
ws.path(),
"parent",
"parent-child",
Epitaph::FinalResponse,
"abc123",
Some("all done\n"),
&FixedClock,
&RecGit::default(),
)
.unwrap();
assert_eq!(
path,
inbox_dir(ws.path(), "parent").join("parent-child-001.md")
);
assert_eq!(
read(&path),
"---\nfrom: parent-child\ndeposited_at: 2026-07-11T00:00:00Z\n\
epitaph: final-response\nterminal_ref: abc123\n---\nall done\n"
);
}
#[test]
fn a_named_sender_is_attributed_by_its_name_beside_its_id() {
let ws = TempDir::new().unwrap();
let path = deposit_result(
ws.path(),
"parent",
"parent-child",
Epitaph::FinalResponse,
"abc123",
Some("spec written\n"),
&FixedClock,
&RecGit {
name: "Speccer",
..RecGit::default()
},
)
.unwrap();
assert_eq!(
read(&path),
"---\nfrom: parent-child\nfrom_name: Speccer\n\
deposited_at: 2026-07-11T00:00:00Z\n\
epitaph: final-response\nterminal_ref: abc123\n---\nspec written\n"
);
assert_eq!(
path,
inbox_dir(ws.path(), "parent").join("parent-child-001.md")
);
}
#[test]
fn result_message_omits_body_when_agent_never_spoke() {
let ws = TempDir::new().unwrap();
let path = deposit_result(
ws.path(),
"parent",
"parent-child",
Epitaph::BudgetExhausted,
"def456",
None,
&FixedClock,
&RecGit::default(),
)
.unwrap();
assert_eq!(
read(&path),
"---\nfrom: parent-child\ndeposited_at: 2026-07-11T00:00:00Z\n\
epitaph: budget-exhausted\nterminal_ref: def456\n---\n"
);
}
#[test]
fn parent_of_strips_the_last_descent_segment() {
assert_eq!(parent_of("20260711T000000Z-a1b2c3d4"), None);
assert_eq!(
parent_of("20260711T000000Z-a1b2c3d4-20260711T000001Z-e5f6a7b8").as_deref(),
Some("20260711T000000Z-a1b2c3d4")
);
assert_eq!(parent_of("r-aa-c-bb-g-cc").as_deref(), Some("r-aa-c-bb"));
assert_eq!(parent_of("a-b"), None);
assert_eq!(parent_of("solo"), None);
}
#[test]
fn a_result_deposit_writes_the_durable_returned_mark() {
let ws = TempDir::new().unwrap();
let git = RecGit::default();
deposit_result(
ws.path(),
"parent",
"parent-child",
Epitaph::Died,
"tipsha",
None,
&FixedClock,
&git,
)
.unwrap();
assert_eq!(
*git.runs.borrow(),
vec![vec![
"update-ref".to_string(),
returned_ref("parent-child"),
"tipsha".to_string(),
]]
);
assert_eq!(returned_ref("x-y"), "refs/litany/returned/x-y");
}
#[test]
fn a_failed_mark_write_surfaces_after_the_file_landed() {
let ws = TempDir::new().unwrap();
let git = RecGit {
fail: true,
..RecGit::default()
};
let err = deposit_result(
ws.path(),
"parent",
"parent-child",
Epitaph::FinalResponse,
"tipsha",
Some("done"),
&FixedClock,
&git,
)
.unwrap_err();
assert!(
err.to_string()
.contains("mark refs/litany/returned/parent-child"),
"unexpected error: {err}"
);
assert!(
inbox_dir(ws.path(), "parent")
.join("parent-child-001.md")
.exists(),
"the deposit itself must survive a failed mark"
);
}