use super::fixtures::{
ErrFinder, ErrInspector, NoopGit, OwnGroupFinder, StubFinder, StubInspector, touch_inbox_dir,
};
use crate::prompt::stop::{Error, cascade, run};
use crate::template::GitRunner;
use std::io;
use std::path::Path;
use std::time::Duration;
use tempfile::TempDir;
#[test]
fn run_propagates_inspector_exists_error_as_git() {
let dir = TempDir::new().unwrap();
let err = run(
dir.path(),
"br",
false,
&ErrInspector,
&StubFinder::default(),
&cascade::RecordingSignaler::new(0),
Duration::from_millis(1),
&NoopGit,
)
.unwrap_err();
matches!(
err,
Error::Git {
op: "rev-parse --verify",
..
}
);
}
#[test]
fn run_propagates_finder_io_error_as_proc() {
let dir = TempDir::new().unwrap();
touch_inbox_dir(dir.path(), "br");
let inspector = StubInspector { exists: true };
let err = run(
dir.path(),
"br",
false,
&inspector,
&ErrFinder,
&cascade::RecordingSignaler::new(0),
Duration::from_millis(1),
&NoopGit,
)
.unwrap_err();
matches!(err, Error::Proc(_));
}
#[test]
fn collect_inbox_dirs_returns_empty_when_inbox_root_missing() {
let dir = TempDir::new().unwrap();
let v = super::super::collect_inbox_dirs(dir.path(), "br", false).unwrap();
assert!(v.is_empty());
}
#[test]
fn collect_inbox_dirs_skips_a_non_utf8_entry() {
use std::os::unix::ffi::OsStrExt;
let dir = TempDir::new().unwrap();
touch_inbox_dir(dir.path(), "br");
let stray = dir
.path()
.join(super::fixtures::INBOX_DIR)
.join(std::ffi::OsStr::from_bytes(b"\xFF\xFE"));
std::fs::create_dir_all(&stray).unwrap();
let v = super::super::collect_inbox_dirs(dir.path(), "br", true).unwrap();
assert_eq!(v.len(), 1, "only the named branch: {v:?}");
assert!(v[0].ends_with("inbox/br"));
}
#[test]
fn collect_inbox_dirs_gates_descendants_on_stop_children() {
let dir = TempDir::new().unwrap();
touch_inbox_dir(dir.path(), "br");
touch_inbox_dir(dir.path(), "br-sub");
let mut default_only = super::super::collect_inbox_dirs(dir.path(), "br", false).unwrap();
default_only.sort();
assert_eq!(
default_only.len(),
1,
"default is self-only: {default_only:?}"
);
assert!(default_only[0].ends_with("inbox/br"));
let mut with_children = super::super::collect_inbox_dirs(dir.path(), "br", true).unwrap();
with_children.sort();
assert_eq!(
with_children.len(),
2,
"flag includes the child: {with_children:?}"
);
assert!(with_children[0].ends_with("inbox/br"));
assert!(with_children[1].ends_with("inbox/br-sub"));
}
#[test]
fn error_display_branch_missing_includes_name() {
let e = Error::BranchMissing("foo".into());
let s = format!("{e}");
assert!(s.contains("\"foo\""), "got {s}");
}
#[test]
fn error_display_inbox_walk_passes_through_io_message() {
let e = Error::InboxWalk(io::Error::other("nope"));
let s = format!("{e}");
assert!(s.contains("nope"), "got {s}");
}
#[test]
fn noop_git_returns_ok_from_both_methods() {
let g = NoopGit;
g.run(Path::new("/anywhere"), &["any", "args"]).unwrap();
assert_eq!(g.run_capture(Path::new("/anywhere"), &["any"]).unwrap(), "");
}
#[test]
fn cli_run_guards_the_layout_before_anything_else() {
let dir = TempDir::new().unwrap();
let err = super::super::cli_run(dir.path(), "no-such-branch", false).unwrap_err();
assert!(matches!(err, Error::Layout(_)), "{err}");
}
#[test]
fn cli_run_returns_branch_missing_against_a_real_workspace() {
let (_h, ws) = crate::workspace::fixture::workspace();
let err = super::super::cli_run(&ws, "no-such-branch", false).unwrap_err();
assert!(
matches!(err, Error::BranchMissing(ref b) if b == "no-such-branch"),
"{err}"
);
let err = super::super::cli_run(&ws, "no-such-branch", true).unwrap_err();
assert!(
matches!(err, Error::BranchMissing(ref b) if b == "no-such-branch"),
"{err}"
);
}
#[test]
fn run_refuses_to_signal_the_stop_process_own_group() {
super::super::become_pgid_leader();
let own = unsafe { libc::getpgrp() };
let dir = TempDir::new().unwrap();
touch_inbox_dir(dir.path(), "br");
let signaler = cascade::RecordingSignaler::new(0);
let err = run(
dir.path(),
"br",
false,
&StubInspector { exists: true },
&OwnGroupFinder,
&signaler,
Duration::from_millis(1),
&NoopGit,
)
.unwrap_err();
assert!(
matches!(err, Error::SelfGroup { pgid } if pgid == own),
"{err}"
);
assert!(
signaler.took().is_empty(),
"refusal must precede the cascade — not one signal may escape"
);
let rendered = err.to_string();
assert!(
rendered.contains("nothing was signalled"),
"the error must tell the operator no signal landed: {rendered}"
);
}
#[test]
fn become_pgid_leader_returns_cleanly() {
super::super::become_pgid_leader();
}
#[test]
fn become_pgid_leader_with_zero_succeeds_silently() {
super::super::become_pgid_leader_with(|| 0);
}
#[test]
fn become_pgid_leader_with_nonzero_takes_error_branch() {
super::super::become_pgid_leader_with(|| -1);
}