use dirigent_fermata::core::{Decision, Reason}; use dirigent_fermata::harness::{HarnessAdapter, PathKind, ToolOp}; use dirigent_fermata::harness::claude::ClaudeAdapter; #[test] fn parses_read_payload() { let payload = br#"{"tool_name":"Read","tool_input":{"file_path":"/proj/.env"}}"#; let call = ClaudeAdapter.parse_request(payload).unwrap(); assert_eq!(call.tool_name, "Read"); match call.op { ToolOp::Path { path, kind } => { assert_eq!(path.to_string_lossy(), "/proj/.env"); assert_eq!(kind, PathKind::Read); } _ => panic!("expected Path op"), } } #[test] fn parses_write_payload() { let payload = br#"{"tool_name":"Write","tool_input":{"file_path":"/proj/out.txt"}}"#; let call = ClaudeAdapter.parse_request(payload).unwrap(); assert!(matches!(call.op, ToolOp::Path { kind: PathKind::Write, .. })); } #[test] fn parses_edit_as_write() { let payload = br#"{"tool_name":"Edit","tool_input":{"file_path":"/proj/src.rs"}}"#; let call = ClaudeAdapter.parse_request(payload).unwrap(); assert!(matches!(call.op, ToolOp::Path { kind: PathKind::Write, .. })); } #[test] fn parses_multiedit_as_write() { let payload = br#"{"tool_name":"MultiEdit","tool_input":{"file_path":"/proj/src.rs","edits":[]}}"#; let call = ClaudeAdapter.parse_request(payload).unwrap(); assert!(matches!(call.op, ToolOp::Path { kind: PathKind::Write, .. })); } #[test] fn parses_bash_payload() { let payload = br#"{"tool_name":"Bash","tool_input":{"command":"rm -rf /"}}"#; let call = ClaudeAdapter.parse_request(payload).unwrap(); match call.op { ToolOp::Command { text } => assert_eq!(text, "rm -rf /"), _ => panic!("expected Command op"), } } #[test] fn renders_deny_as_hookspecificoutput() { let payload = br#"{"tool_name":"Read","tool_input":{"file_path":"/proj/.env"}}"#; let call = ClaudeAdapter.parse_request(payload).unwrap(); let d = Decision::Deny(Reason { message: "blocked by .botignore: .env".into(), rule: None, }); let out = ClaudeAdapter.render_decision(&call, &d).unwrap(); let v: serde_json::Value = serde_json::from_slice(&out).unwrap(); assert_eq!(v["hookSpecificOutput"]["hookEventName"], "PreToolUse"); assert_eq!(v["hookSpecificOutput"]["permissionDecision"], "deny"); assert!(v["hookSpecificOutput"]["permissionDecisionReason"] .as_str() .unwrap() .contains(".env")); } #[test] fn renders_allow_as_allow() { let payload = br#"{"tool_name":"Read","tool_input":{"file_path":"/proj/src/main.rs"}}"#; let call = ClaudeAdapter.parse_request(payload).unwrap(); let out = ClaudeAdapter.render_decision(&call, &Decision::Allow).unwrap(); let v: serde_json::Value = serde_json::from_slice(&out).unwrap(); assert_eq!(v["hookSpecificOutput"]["permissionDecision"], "allow"); } #[test] fn renders_ask_as_ask() { let payload = br#"{"tool_name":"Bash","tool_input":{"command":"rm something"}}"#; let call = ClaudeAdapter.parse_request(payload).unwrap(); let out = ClaudeAdapter .render_decision(&call, &Decision::Ask(Reason { message: "confirm".into(), rule: None })) .unwrap(); let v: serde_json::Value = serde_json::from_slice(&out).unwrap(); assert_eq!(v["hookSpecificOutput"]["permissionDecision"], "ask"); }