Codex Handbook
config/src/hooks_tests.rs 264 lines
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()        }    );}