use pretty_assertions::assert_eq; use std::collections::BTreeMap; use super::HookEventsToml; use super::HookHandlerConfig; use super::HooksFile; use super::HooksToml; use super::ManagedHooksRequirementsToml; use super::MatcherGroup; #[test] fn hooks_file_deserializes_existing_json_shape() { let parsed: HooksFile = serde_json::from_str( r#"{ "hooks": { "PreToolUse": [ { "matcher": "^Bash$", "hooks": [ { "type": "command", "command": "python3 /tmp/pre.py", "timeout": 10, "statusMessage": "checking" } ] } ] } }"#, ) .expect("hooks.json should deserialize"); assert_eq!( parsed, HooksFile { hooks: HookEventsToml { pre_tool_use: vec![MatcherGroup { matcher: Some("^Bash$".to_string()), hooks: vec![HookHandlerConfig::Command { command: "python3 /tmp/pre.py".to_string(), command_windows: None, timeout_sec: Some(10), r#async: false, status_message: Some("checking".to_string()), }], }], ..Default::default() }, } ); } #[test] fn hook_events_deserialize_from_toml_arrays_of_tables() { let parsed: HookEventsToml = toml::from_str( r#" [[PreToolUse]] matcher = "^Bash$" [[PreToolUse.hooks]] type = "command" command = "python3 /tmp/pre.py" timeout = 10 statusMessage = "checking" "#, ) .expect("hook events TOML should deserialize"); assert_eq!( parsed, HookEventsToml { pre_tool_use: vec![MatcherGroup { matcher: Some("^Bash$".to_string()), hooks: vec![HookHandlerConfig::Command { command: "python3 /tmp/pre.py".to_string(), command_windows: None, timeout_sec: Some(10), r#async: false, status_message: Some("checking".to_string()), }], }], ..Default::default() } ); } #[test] fn hooks_toml_deserializes_inline_events_and_state_map() { let parsed: HooksToml = toml::from_str( r#" [state."/tmp/hooks.json:pre_tool_use:0:0"] enabled = false trusted_hash = "sha256:abc123" [[PreToolUse]] matcher = "^Bash$" [[PreToolUse.hooks]] type = "command" command = "python3 /tmp/pre.py" "#, ) .expect("hooks TOML should deserialize"); assert_eq!( parsed, HooksToml { events: HookEventsToml { pre_tool_use: vec![MatcherGroup { matcher: Some("^Bash$".to_string()), hooks: vec![HookHandlerConfig::Command { command: "python3 /tmp/pre.py".to_string(), command_windows: None, timeout_sec: None, r#async: false, status_message: None, }], }], ..Default::default() }, state: BTreeMap::from([( "/tmp/hooks.json:pre_tool_use:0:0".to_string(), super::HookStateToml { enabled: Some(false), trusted_hash: Some("sha256:abc123".to_string()), }, )]), } ); } #[test] fn managed_hooks_requirements_flatten_hook_events() { let parsed: ManagedHooksRequirementsToml = toml::from_str( r#" managed_dir = "/enterprise/place" [[PreToolUse]] matcher = "^Bash$" [[PreToolUse.hooks]] type = "command" command = "python3 /enterprise/place/pre.py" "#, ) .expect("requirements hooks TOML should deserialize"); assert_eq!( parsed, ManagedHooksRequirementsToml { managed_dir: Some(std::path::PathBuf::from("/enterprise/place")), windows_managed_dir: None, hooks: HookEventsToml { pre_tool_use: vec![MatcherGroup { matcher: Some("^Bash$".to_string()), hooks: vec![HookHandlerConfig::Command { command: "python3 /enterprise/place/pre.py".to_string(), command_windows: None, timeout_sec: None, r#async: false, status_message: None, }], }], ..Default::default() }, } ); } #[test] fn hook_events_deserialize_windows_override_from_toml() { let parsed: HookEventsToml = toml::from_str( r#" [[PreToolUse]] matcher = "^Bash$" [[PreToolUse.hooks]] type = "command" command = "bash /enterprise/hooks/pre.sh" command_windows = "powershell -File C:\\enterprise\\hooks\\pre.ps1" "#, ) .expect("hook command Windows override TOML should deserialize"); assert_eq!( parsed, HookEventsToml { pre_tool_use: vec![MatcherGroup { matcher: Some("^Bash$".to_string()), hooks: vec![HookHandlerConfig::Command { command: "bash /enterprise/hooks/pre.sh".to_string(), command_windows: Some( r"powershell -File C:\enterprise\hooks\pre.ps1".to_string(), ), timeout_sec: None, r#async: false, status_message: None, }], }], ..Default::default() } ); } #[test] fn hook_events_deserialize_camel_case_windows_override_from_toml() { let parsed: HookEventsToml = toml::from_str( r#" [[PreToolUse]] matcher = "^Bash$" [[PreToolUse.hooks]] type = "command" command = "bash /enterprise/hooks/pre.sh" commandWindows = "powershell -File C:\\enterprise\\hooks\\pre.ps1" "#, ) .expect("camelCase hook command Windows override TOML should deserialize"); assert_eq!( parsed, HookEventsToml { pre_tool_use: vec![MatcherGroup { matcher: Some("^Bash$".to_string()), hooks: vec![HookHandlerConfig::Command { command: "bash /enterprise/hooks/pre.sh".to_string(), command_windows: Some( r"powershell -File C:\enterprise\hooks\pre.ps1".to_string(), ), timeout_sec: None, r#async: false, status_message: None, }], }], ..Default::default() } ); }