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 hooks_file_rejects_events_outside_hooks_object() { let error = serde_json::from_str::<HooksFile>( r#"{ "SessionStart": [ { "hooks": [ { "type": "command", "command": "python3 /tmp/session_start.py" } ] } ]}"#, ) .expect_err("root-level hook events should be rejected"); assert!( error.to_string().contains("unknown field `SessionStart`"), "unexpected parse error: {error}" );}#[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 = 10statusMessage = "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 = falsetrusted_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() } );}