#![cfg(unix)]
#![allow(clippy::unwrap_used, clippy::expect_used)]
use std::io::{BufRead, BufReader, Write};
use std::os::unix::net::UnixListener;
use std::process::{Command, Output, Stdio};
use std::sync::mpsc;
use std::thread;
use std::time::Duration;
const PERMISSION_REQUEST: &str = r#"{"session_id":"s1","cwd":"/tmp","hook_event_name":"PermissionRequest","tool_name":"Bash","tool_input":{"command":"rm -rf /"}}"#;
const ELICITATION: &str = r#"{"session_id":"s1","cwd":"/tmp","hook_event_name":"Elicitation","server_name":"srv","elicitation_prompt":"Continue?"}"#;
fn run_sink(socket: &std::path::Path, home: &std::path::Path, agent: &str, input: &str) -> Output {
let mut child = Command::new(env!("CARGO_BIN_EXE_omni-dev"))
.args(["sessions", "hook", "--agent", agent, "--socket"])
.arg(socket)
.env("HOME", home)
.env("OMNI_DEV_LOG_DISABLE", "1")
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn()
.expect("failed to run binary");
child
.stdin
.take()
.unwrap()
.write_all(input.as_bytes())
.unwrap();
child.wait_with_output().unwrap()
}
fn assert_silent_success(output: &Output) {
assert_eq!(output.status.code(), Some(0), "the sink must always exit 0");
assert!(
output.stdout.is_empty(),
"the sink must print nothing on an answerable event, got: {}",
String::from_utf8_lossy(&output.stdout)
);
}
#[test]
fn permission_request_sink_is_silent_when_the_daemon_replies_with_a_decision() {
let dir = tempfile::tempdir().unwrap();
let socket = dir.path().join("d.sock");
let listener = UnixListener::bind(&socket).unwrap();
let (tx, rx) = mpsc::channel();
thread::spawn(move || {
let (stream, _) = listener.accept().unwrap();
let mut reader = BufReader::new(stream.try_clone().unwrap());
let mut request = String::new();
reader.read_line(&mut request).unwrap();
let mut writer = stream;
writer
.write_all(
br#"{"ok":true,"payload":{"behavior":"allow","hookSpecificOutput":{"hookEventName":"PermissionRequest","decision":{"behavior":"allow"}}}}"#,
)
.unwrap();
writer.write_all(b"\n").unwrap();
let _ = tx.send(request);
});
let output = run_sink(&socket, dir.path(), "claude", PERMISSION_REQUEST);
assert_silent_success(&output);
let request = rx
.recv_timeout(Duration::from_secs(10))
.expect("the sink never reached the fake daemon");
let request: serde_json::Value = serde_json::from_str(request.trim()).unwrap();
assert_eq!(request["service"], "sessions");
assert_eq!(request["op"], "observe");
assert_eq!(
request["payload"]["event"]["notification"],
"permission_prompt"
);
}
#[test]
fn answerable_events_are_silent_with_no_daemon() {
let dir = tempfile::tempdir().unwrap();
let socket = dir.path().join("absent.sock");
for agent in ["claude", "codex"] {
for input in [PERMISSION_REQUEST, ELICITATION] {
let output = run_sink(&socket, dir.path(), agent, input);
assert_silent_success(&output);
}
}
}