use super::*;
use crate::cli_outbound::Cli;
use crate::projects::balls::parse_list;
use crate::test_support::spawn_guard;
use std::os::unix::fs::PermissionsExt;
use std::path::PathBuf;
use std::sync::MutexGuard;
use tempfile::tempdir;
struct FakeRunner {
reply: Option<String>,
}
impl FakeRunner {
fn answer(&self) -> io::Result<String> {
self.reply.clone().ok_or_else(|| io::Error::other("boom"))
}
}
impl BlRunner for FakeRunner {
fn list(&self, _project: &Path) -> io::Result<String> {
self.answer()
}
fn list_closed(&self, _project: &Path) -> io::Result<String> {
self.answer()
}
fn show(&self, _project: &Path, _id: &str) -> io::Result<String> {
self.answer()
}
}
fn write_bl(dir: &Path, body: &str) -> (PathBuf, MutexGuard<'static, ()>) {
let guard = spawn_guard();
let path = dir.join("bl");
std::fs::write(&path, body).unwrap();
let mut perms = std::fs::metadata(&path).unwrap().permissions();
perms.set_mode(0o755);
std::fs::set_permissions(&path, perms).unwrap();
(path, guard)
}
#[test]
fn blcli_runs_bl_json_and_returns_stdout_ignoring_stderr() {
let dir = tempdir().unwrap();
let (bin, _g) = write_bl(
dir.path(),
"#!/bin/sh\nprintf 'warn\\n' 1>&2\nprintf '[{\"id\":\"bl-1\"}]\\n'\n",
);
let runner = BlCli::new(Cli::new(bin));
let out = runner.list(dir.path()).unwrap();
assert_eq!(parse_list(&out).len(), 1);
assert!(runner.list_closed(dir.path()).is_ok());
assert!(runner.show(dir.path(), "bl-1").is_ok());
}
#[test]
fn blcli_errors_on_spawn_failure() {
let dir = tempdir().unwrap();
let _g = spawn_guard();
let missing = BlCli::new(Cli::new(dir.path().join("no-such-bl")));
assert!(missing.list(dir.path()).is_err());
}
#[test]
fn blcli_errors_on_nonzero_exit() {
let dir = tempdir().unwrap();
let (bin, _g) = write_bl(dir.path(), "#!/bin/sh\nprintf 'partial'\nexit 3\n");
assert!(BlCli::new(Cli::new(bin)).list(dir.path()).is_err());
}
#[test]
fn live_closed_and_detail_parse_or_default_forgivingly() {
let p = Path::new("/p");
let ok = FakeRunner {
reply: Some(r#"[{"id":"bl-1"}]"#.to_owned()),
};
assert_eq!(ok.live(p).len(), 1);
assert_eq!(ok.closed(p).len(), 1);
let err = FakeRunner { reply: None };
assert!(err.live(p).is_empty(), "runner error ⇒ no balls");
assert!(err.closed(p).is_empty());
let one = FakeRunner {
reply: Some(r#"{"id":"bl-1","title":"T"}"#.to_owned()),
};
assert_eq!(one.detail(p, "bl-1").unwrap().title, "T");
assert!(err.detail(p, "bl-1").is_none(), "runner error ⇒ None");
let garbage = FakeRunner {
reply: Some("nope".to_owned()),
};
assert!(garbage.detail(p, "bl-1").is_none(), "unparseable ⇒ None");
}
#[test]
fn identity_prefers_recorded_then_user_then_empty() {
assert_eq!(
identity(Some("recorded".to_owned()), Some("user".to_owned())),
"recorded"
);
assert_eq!(identity(None, Some("user".to_owned())), "user");
assert_eq!(identity(None, None), "");
}