Download codex-rs/config/src/hooks_tests.rs from SaylorTwift/codex: direct link, hf CLI and curl.
- Browser
- Download file 10.7 kB
-
https://huggingface.co/SaylorTwift/codex/resolve/main/codex-rs/config/src/hooks_tests.rs
- Command line
-
hf download hf://SaylorTwift/codex/codex-rs/config/src/hooks_tests.rs
-
curl -L -o hooks_tests.rs https://huggingface.co/SaylorTwift/codex/resolve/main/codex-rs/config/src/hooks_tests.rs
10.7 kB
| 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; | |
| fn hooks_file_deserializes_existing_json_shape() { | |
| let parsed: HooksFile = serde_json::from_str( | |
| r#"{ | |
| "description": "Optional stop-time review gate for Codex Companion.", | |
| "hooks": { | |
| "PreToolUse": [ | |
| { | |
| "matcher": "^Bash$", | |
| "hooks": [ | |
| { | |
| "type": "command", | |
| "command": "python3 /tmp/pre.py", | |
| "timeout": 10, | |
| "statusMessage": "checking", | |
| "additionalContextLimit": 4096 | |
| } | |
| ] | |
| } | |
| ] | |
| } | |
| }"#, | |
| ) | |
| .expect("hooks.json should deserialize"); | |
| assert_eq!( | |
| parsed, | |
| HooksFile { | |
| description: Some("Optional stop-time review gate for Codex Companion.".to_string()), | |
| 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()), | |
| additional_context_limit: Some(4096), | |
| }], | |
| }], | |
| ..Default::default() | |
| }, | |
| } | |
| ); | |
| } | |
| fn hooks_file_deserializes_mcp_tool_handler_with_json_inputs() { | |
| let parsed: HooksFile = serde_json::from_value(serde_json::json!({ | |
| "hooks": { | |
| "PostToolUse": [{ | |
| "matcher": "Write|Edit", | |
| "hooks": [{ | |
| "type": "mcp_tool", | |
| "server": "security", | |
| "tool": "scan", | |
| "input": { | |
| "file_path": "${tool_input.file_path}", | |
| "include_ignored": false, | |
| }, | |
| "timeout": 30, | |
| "statusMessage": "Scanning file", | |
| }], | |
| }], | |
| }, | |
| })) | |
| .expect("MCP tool hooks should deserialize"); | |
| assert_eq!( | |
| parsed.hooks.post_tool_use[0].hooks, | |
| vec![HookHandlerConfig::McpTool { | |
| server: "security".to_string(), | |
| tool: "scan".to_string(), | |
| input: serde_json::Map::from_iter([ | |
| ( | |
| "file_path".to_string(), | |
| serde_json::Value::String("${tool_input.file_path}".to_string()), | |
| ), | |
| ( | |
| "include_ignored".to_string(), | |
| serde_json::Value::Bool(false) | |
| ), | |
| ]), | |
| timeout_sec: Some(30), | |
| status_message: Some("Scanning file".to_string()), | |
| }] | |
| ); | |
| } | |
| fn hooks_file_rejects_mcp_tool_handler_with_null_input() { | |
| for input in [ | |
| serde_json::json!({ "optional": null }), | |
| serde_json::json!({ "metadata": { "optional": null } }), | |
| serde_json::json!({ "values": [null] }), | |
| ] { | |
| let error = serde_json::from_value::<HooksFile>(serde_json::json!({ | |
| "hooks": { | |
| "PostToolUse": [{ | |
| "hooks": [{ | |
| "type": "mcp_tool", | |
| "server": "security", | |
| "tool": "scan", | |
| "input": input, | |
| }], | |
| }], | |
| }, | |
| })) | |
| .expect_err("literal null MCP hook arguments should be rejected"); | |
| assert!( | |
| error | |
| .to_string() | |
| .contains("MCP hook input must be representable as TOML"), | |
| "unexpected parse error: {error}" | |
| ); | |
| } | |
| } | |
| 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}" | |
| ); | |
| } | |
| 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" | |
| additionalContextLimit = 4096 | |
| "#, | |
| ) | |
| .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()), | |
| additional_context_limit: Some(4096), | |
| }], | |
| }], | |
| ..Default::default() | |
| } | |
| ); | |
| } | |
| 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, | |
| additional_context_limit: 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()), | |
| }, | |
| )]), | |
| } | |
| ); | |
| } | |
| 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, | |
| additional_context_limit: None, | |
| }], | |
| }], | |
| ..Default::default() | |
| }, | |
| } | |
| ); | |
| } | |
| 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, | |
| additional_context_limit: None, | |
| }], | |
| }], | |
| ..Default::default() | |
| } | |
| ); | |
| } | |
| 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, | |
| additional_context_limit: None, | |
| }], | |
| }], | |
| ..Default::default() | |
| } | |
| ); | |
| } | |
| fn hook_handler_omits_unset_additional_context_limit() { | |
| let handler = HookHandlerConfig::Command { | |
| command: "python3 /tmp/pre.py".to_string(), | |
| command_windows: None, | |
| timeout_sec: None, | |
| r#async: false, | |
| status_message: None, | |
| additional_context_limit: None, | |
| }; | |
| let serialized = serde_json::to_value(handler).expect("hook handler should serialize"); | |
| assert_eq!(serialized.get("additionalContextLimit"), None); | |
| } | |