use super::super::drain::SeenDeposit;
use super::{release_then_reprobe, reprobe_after_release};
use crate::prompt::SystemClock;
use crate::prompt::inbox::{
Epitaph, Launcher, ProbeOutcome, deposit, deposit_result, inbox_dir, probe_and_launch,
try_acquire,
};
use std::cell::RefCell;
use std::io;
use std::path::Path;
use tempfile::TempDir;
fn seen_of(path: &Path) -> SeenDeposit {
SeenDeposit::new(
path.file_name().unwrap().to_string_lossy().into_owned(),
std::fs::metadata(path).unwrap().modified().unwrap(),
)
}
const AGENT: &str = "20260101-a1";
struct OkGit;
impl crate::template::GitRunner for OkGit {
fn run(&self, _dest: &Path, _args: &[&str]) -> io::Result<()> {
Ok(())
}
fn run_capture(&self, _dest: &Path, _args: &[&str]) -> io::Result<String> {
Ok(String::new())
}
}
#[derive(Default)]
struct RecLauncher {
launches: RefCell<Vec<String>>,
}
impl Launcher for RecLauncher {
fn launch(&self, _ws: &Path, agent: &str) -> io::Result<()> {
self.launches.borrow_mut().push(agent.to_string());
Ok(())
}
}
struct FailLauncher;
impl Launcher for FailLauncher {
fn launch(&self, _ws: &Path, _agent: &str) -> io::Result<()> {
Err(io::Error::other("spawn refused"))
}
}
#[test]
fn a_deposit_racing_the_holders_last_read_is_launched_at_release() {
let ws = TempDir::new().unwrap();
let holder = try_acquire(&inbox_dir(ws.path(), AGENT))
.unwrap()
.expect("free");
let seen: Vec<SeenDeposit> = Vec::new();
deposit(ws.path(), AGENT, "user", "racing mail", &SystemClock).unwrap();
let rec = RecLauncher::default();
assert_eq!(
probe_and_launch(ws.path(), AGENT, &rec).unwrap(),
ProbeOutcome::Busy,
"the writer's probe must defer to the live holder"
);
assert!(rec.launches.borrow().is_empty());
release_then_reprobe(holder, ws.path(), AGENT, &seen, &rec);
assert_eq!(*rec.launches.borrow(), vec![AGENT.to_string()]);
assert_eq!(
std::fs::read_dir(inbox_dir(ws.path(), AGENT))
.unwrap()
.count(),
1
);
}
#[test]
fn mail_the_holders_last_read_saw_never_relaunches() {
let ws = TempDir::new().unwrap();
let holder = try_acquire(&inbox_dir(ws.path(), AGENT))
.unwrap()
.expect("free");
let held = deposit(ws.path(), AGENT, "user", "held mail", &SystemClock).unwrap();
let seen = vec![seen_of(&held)];
let rec = RecLauncher::default();
release_then_reprobe(holder, ws.path(), AGENT, &seen, &rec);
assert!(rec.launches.borrow().is_empty());
}
#[test]
fn a_reused_name_is_a_new_deposit_and_fires_the_release() {
let ws = TempDir::new().unwrap();
let holder = try_acquire(&inbox_dir(ws.path(), AGENT))
.unwrap()
.expect("free");
let racing = deposit(ws.path(), AGENT, "user", "racing mail", &SystemClock).unwrap();
let seen = vec![SeenDeposit::new(
racing.file_name().unwrap().to_string_lossy().into_owned(),
std::time::SystemTime::UNIX_EPOCH,
)];
let rec = RecLauncher::default();
release_then_reprobe(holder, ws.path(), AGENT, &seen, &rec);
assert_eq!(*rec.launches.borrow(), vec![AGENT.to_string()]);
}
#[test]
fn a_rival_holding_the_freed_lease_defers_the_reprobe() {
let ws = TempDir::new().unwrap();
let _rival = try_acquire(&inbox_dir(ws.path(), AGENT))
.unwrap()
.expect("free");
deposit(ws.path(), AGENT, "user", "new mail", &SystemClock).unwrap();
let rec = RecLauncher::default();
reprobe_after_release(ws.path(), AGENT, &[], &rec);
assert!(rec.launches.borrow().is_empty());
}
#[test]
fn a_racing_result_is_completed_per_its_own_epitaph_warrant() {
let child = "20260101-a1-20260102-b2";
for (epitaph, launches) in [
(Epitaph::FinalResponse, true),
(Epitaph::Died, true),
(Epitaph::Stopped, false),
(Epitaph::BudgetExhausted, false),
] {
let ws = TempDir::new().unwrap();
deposit_result(
ws.path(),
AGENT,
child,
epitaph,
"abc123",
None,
&SystemClock,
&OkGit,
)
.unwrap();
let rec = RecLauncher::default();
reprobe_after_release(ws.path(), AGENT, &[], &rec);
assert_eq!(
!rec.launches.borrow().is_empty(),
launches,
"epitaph {:?} must {}launch",
epitaph,
if launches { "" } else { "not " }
);
}
}
#[test]
fn an_illegible_deposit_body_launches_rather_than_classifies() {
let ws = TempDir::new().unwrap();
std::fs::create_dir_all(inbox_dir(ws.path(), AGENT).join("user-001.md")).unwrap();
let rec = RecLauncher::default();
reprobe_after_release(ws.path(), AGENT, &[], &rec);
assert_eq!(*rec.launches.borrow(), vec![AGENT.to_string()]);
}
#[test]
fn an_unreadable_inbox_reread_is_swallowed() {
let ws = TempDir::new().unwrap();
std::fs::create_dir_all(ws.path().join("inbox")).unwrap();
std::fs::write(inbox_dir(ws.path(), AGENT), b"not a dir").unwrap();
let rec = RecLauncher::default();
reprobe_after_release(ws.path(), AGENT, &[], &rec);
assert!(rec.launches.borrow().is_empty());
}
#[test]
fn a_failed_post_release_launch_is_swallowed() {
let ws = TempDir::new().unwrap();
deposit(ws.path(), AGENT, "user", "mail", &SystemClock).unwrap();
reprobe_after_release(ws.path(), AGENT, &[], &FailLauncher);
}