#![allow(clippy::expect_used, clippy::unwrap_used)]
use std::io::Write;
use std::process::{Command, Stdio};
use std::time::Duration;
use tazuna::hooks::{HookEventType, HooksServer};
use tazuna::session::SessionId;
use tokio::sync::mpsc;
use uuid::Uuid;
fn spawn_notify(session_id: &SessionId, socket_path: &std::path::Path) -> std::process::Child {
Command::new(env!("CARGO_BIN_EXE_tazuna"))
.arg("notify")
.env("TAZUNA_SESSION_ID", session_id.to_string())
.env("TAZUNA_SOCKET_PATH", socket_path.to_str().unwrap())
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn()
.expect("spawn tazuna notify")
}
#[tokio::test]
async fn tazuna_notify_sends_event_to_server() {
let temp = tempfile::tempdir().expect("create tempdir");
let socket_path = temp.path().join("test.sock");
let (tx, mut rx) = mpsc::channel(16);
let server = HooksServer::new(&socket_path, tx).expect("create server");
let server_handle = tokio::spawn(server.run());
tokio::time::sleep(Duration::from_millis(50)).await;
let session_id = SessionId::from(Uuid::new_v4());
let json_input = r#"{"hook_event_name": "Notification", "message": "Test notification"}"#;
let mut child = spawn_notify(&session_id, &socket_path);
child
.stdin
.as_mut()
.unwrap()
.write_all(json_input.as_bytes())
.expect("write stdin");
drop(child.stdin.take());
let status = child.wait().expect("wait");
assert!(status.success(), "tazuna notify should succeed");
let event = tokio::time::timeout(Duration::from_secs(1), rx.recv())
.await
.expect("should receive within timeout")
.expect("should receive event");
assert_eq!(event.event_type, HookEventType::Notification);
assert_eq!(event.session_id, session_id);
assert_eq!(event.message(), Some("Test notification".to_string()));
server_handle.abort();
}
#[tokio::test]
async fn tazuna_notify_pre_tool_use() {
let temp = tempfile::tempdir().expect("create tempdir");
let socket_path = temp.path().join("test.sock");
let (tx, mut rx) = mpsc::channel(16);
let server = HooksServer::new(&socket_path, tx).expect("create server");
let server_handle = tokio::spawn(server.run());
tokio::time::sleep(Duration::from_millis(50)).await;
let session_id = SessionId::from(Uuid::new_v4());
let json_input = r#"{"hook_event_name": "PreToolUse", "tool_name": "Bash", "tool_input": {"command": "ls"}}"#;
let mut child = spawn_notify(&session_id, &socket_path);
child
.stdin
.as_mut()
.unwrap()
.write_all(json_input.as_bytes())
.expect("write stdin");
drop(child.stdin.take());
let status = child.wait().expect("wait");
assert!(status.success());
let event = tokio::time::timeout(Duration::from_secs(1), rx.recv())
.await
.expect("timeout")
.expect("receive event");
assert_eq!(event.event_type, HookEventType::PreToolUse);
assert_eq!(event.message(), Some("Tool: Bash".to_string()));
server_handle.abort();
}
#[tokio::test]
async fn tazuna_notify_missing_session_id() {
let temp = tempfile::tempdir().expect("create tempdir");
let socket_path = temp.path().join("test.sock");
let mut child = Command::new(env!("CARGO_BIN_EXE_tazuna"))
.arg("notify")
.env("TAZUNA_SOCKET_PATH", socket_path.to_str().unwrap())
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn()
.expect("spawn");
child
.stdin
.as_mut()
.unwrap()
.write_all(b"{\"hook\": \"Notification\", \"message\": \"test\"}")
.expect("write");
drop(child.stdin.take());
let status = child.wait().expect("wait");
assert!(!status.success(), "should fail without TAZUNA_SESSION_ID");
}
#[tokio::test]
async fn tazuna_notify_invalid_json() {
let temp = tempfile::tempdir().expect("create tempdir");
let socket_path = temp.path().join("test.sock");
let session_id = SessionId::from(Uuid::new_v4());
let mut child = spawn_notify(&session_id, &socket_path);
child
.stdin
.as_mut()
.unwrap()
.write_all(b"not valid json")
.expect("write");
drop(child.stdin.take());
let status = child.wait().expect("wait");
assert!(!status.success(), "should fail with invalid JSON");
}
#[tokio::test]
async fn tazuna_notify_socket_not_found() {
let temp = tempfile::tempdir().expect("create tempdir");
let socket_path = temp.path().join("nonexistent.sock");
let session_id = SessionId::from(Uuid::new_v4());
let mut child = spawn_notify(&session_id, &socket_path);
child
.stdin
.as_mut()
.unwrap()
.write_all(b"{\"hook\": \"Notification\", \"message\": \"test\"}")
.expect("write");
drop(child.stdin.take());
let status = child.wait().expect("wait");
assert!(!status.success(), "should fail when socket doesn't exist");
}