// End-to-end integration test for plugin hooks use color_eyre::eyre::Result; use plugins::PluginManager; use std::fs; use tempfile::TempDir; fn create_test_plugin_with_hooks(plugin_dir: &std::path::Path) -> Result<()> { fs::create_dir_all(plugin_dir)?; // Create plugin manifest let manifest = serde_json::json!({ "name": "test-hook-plugin", "version": "1.0.0", "description": "Test plugin with hooks", "commands": [], "agents": [], "skills": [], "hooks": {}, "mcp_servers": [] }); fs::write( plugin_dir.join("plugin.json"), serde_json::to_string_pretty(&manifest)?, )?; // Create hooks directory and hooks.json let hooks_dir = plugin_dir.join("hooks"); fs::create_dir_all(&hooks_dir)?; let hooks_config = serde_json::json!({ "description": "Validate edit and write operations", "hooks": { "PreToolUse": [ { "matcher": "Edit|Write", "hooks": [ { "type": "command", "command": "python3 ${CLAUDE_PLUGIN_ROOT}/hooks/validate.py", "timeout": 5000 } ] }, { "matcher": "Bash", "hooks": [ { "type": "command", "command": "echo 'Bash hook' && exit 0" } ] } ], "PostToolUse": [ { "hooks": [ { "type": "command", "command": "echo 'Post-tool hook' && exit 0" } ] } ] } }); fs::write( hooks_dir.join("hooks.json"), serde_json::to_string_pretty(&hooks_config)?, )?; Ok(()) } #[test] fn test_load_plugin_hooks_config() -> Result<()> { let temp_dir = TempDir::new()?; let plugin_dir = temp_dir.path().join("test-plugin"); create_test_plugin_with_hooks(&plugin_dir)?; // Load all plugins let mut plugin_manager = PluginManager::with_dirs(vec![temp_dir.path().to_path_buf()]); plugin_manager.load_all()?; assert_eq!(plugin_manager.plugins().len(), 1); let plugin = &plugin_manager.plugins()[0]; // Load hooks config let hooks_config = plugin.load_hooks_config()?; assert!(hooks_config.is_some()); let config = hooks_config.unwrap(); assert_eq!(config.description, Some("Validate edit and write operations".to_string())); assert!(config.hooks.contains_key("PreToolUse")); assert!(config.hooks.contains_key("PostToolUse")); // Check PreToolUse hooks let pre_tool_hooks = &config.hooks["PreToolUse"]; assert_eq!(pre_tool_hooks.len(), 2); // First matcher: Edit|Write assert_eq!(pre_tool_hooks[0].matcher, Some("Edit|Write".to_string())); assert_eq!(pre_tool_hooks[0].hooks.len(), 1); assert_eq!(pre_tool_hooks[0].hooks[0].hook_type, "command"); assert!(pre_tool_hooks[0].hooks[0].command.as_ref().unwrap().contains("validate.py")); // Second matcher: Bash assert_eq!(pre_tool_hooks[1].matcher, Some("Bash".to_string())); assert_eq!(pre_tool_hooks[1].hooks.len(), 1); Ok(()) } #[test] fn test_plugin_hooks_substitution() -> Result<()> { let temp_dir = TempDir::new()?; let plugin_dir = temp_dir.path().join("test-plugin"); create_test_plugin_with_hooks(&plugin_dir)?; // Load all plugins let mut plugin_manager = PluginManager::with_dirs(vec![temp_dir.path().to_path_buf()]); plugin_manager.load_all()?; assert_eq!(plugin_manager.plugins().len(), 1); let plugin = &plugin_manager.plugins()[0]; // Load hooks config and register let hooks_config = plugin.load_hooks_config()?.unwrap(); let hooks_to_register = plugin.register_hooks_with_manager(&hooks_config); // Check that ${CLAUDE_PLUGIN_ROOT} was substituted let edit_write_hook = hooks_to_register.iter() .find(|(event, _, pattern, _)| { event == "PreToolUse" && pattern.as_ref().map(|p| p.contains("Edit")).unwrap_or(false) }) .unwrap(); // The command should have the plugin path substituted assert!(edit_write_hook.1.contains(&plugin_dir.to_string_lossy().to_string())); assert!(edit_write_hook.1.contains("validate.py")); assert!(!edit_write_hook.1.contains("${CLAUDE_PLUGIN_ROOT}")); Ok(()) } #[test] fn test_multiple_plugins_with_hooks() -> Result<()> { let temp_dir = TempDir::new()?; // Create two plugins with hooks let plugin1_dir = temp_dir.path().join("plugin1"); create_test_plugin_with_hooks(&plugin1_dir)?; let plugin2_dir = temp_dir.path().join("plugin2"); create_test_plugin_with_hooks(&plugin2_dir)?; // Load all plugins let mut plugin_manager = PluginManager::with_dirs(vec![temp_dir.path().to_path_buf()]); plugin_manager.load_all()?; assert_eq!(plugin_manager.plugins().len(), 2); // Collect all hooks from all plugins let mut total_hooks = 0; for plugin in plugin_manager.plugins() { if let Ok(Some(hooks_config)) = plugin.load_hooks_config() { let hooks = plugin.register_hooks_with_manager(&hooks_config); total_hooks += hooks.len(); } } // Each plugin has 3 hooks (2 PreToolUse + 1 PostToolUse) assert_eq!(total_hooks, 6); Ok(()) }