//! Integration tests for finalize hook execution //! //! These tests verify that shell hooks and plugin hooks execute correctly //! through the finalize stage hook executor. use std::collections::HashMap; use std::time::Duration; use workshop_baker::ExecutionContext; use workshop_baker::finalize::plugin::PluginMap; use workshop_baker::finalize::stage::hook::hook; use workshop_engine::{ExecutionEvent, CustomCommand}; fn create_test_context() -> ExecutionContext { ExecutionContext { task_id: "test-hook-1".to_string(), pipeline_name: "test-pipeline".to_string(), username: "testuser".to_string(), working_dir: std::env::temp_dir(), env_vars: HashMap::new(), timeout: Some(Duration::from_secs(30)), shell: "bash".to_string(), dry_run: false, privileged: false, standalone: false, } } fn create_test_event_channel() -> ( Option>, tokio::sync::mpsc::Receiver, ) { let (tx, rx) = tokio::sync::mpsc::channel(64); (Some(tx), rx) } #[tokio::test] async fn test_shell_hook_success() { let ctx = create_test_context(); let (event_tx, _event_rx) = create_test_event_channel(); let plugins = PluginMap::new(); let cmd = CustomCommand { name: "echo-test".to_string(), command: "echo 'hook-ok'".to_string(), environment: None, working_dir: None, timeout_ms: 5000, argument: serde_yaml::Value::Null, }; let result = hook(Some(&vec![cmd]), &ctx, event_tx, &plugins).await; assert!(result.is_ok(), "Shell hook should succeed: {:?}", result); } #[tokio::test] async fn test_shell_hook_failure_propagates() { let ctx = create_test_context(); let (event_tx, _event_rx) = create_test_event_channel(); let plugins = PluginMap::new(); let cmd = CustomCommand { name: "false-cmd".to_string(), command: "false".to_string(), environment: None, working_dir: None, timeout_ms: 5000, argument: serde_yaml::Value::Null, }; let result = hook(Some(&vec![cmd]), &ctx, event_tx, &plugins).await; assert!(result.is_err(), "Shell hook failure should propagate"); } #[tokio::test] async fn test_hook_timeout_kills_long_command() { let ctx = create_test_context(); let (event_tx, _event_rx) = create_test_event_channel(); let plugins = PluginMap::new(); let cmd = CustomCommand { name: "sleep-forever".to_string(), command: "sleep 10".to_string(), environment: None, working_dir: None, timeout_ms: 500, // 0.5 seconds argument: serde_yaml::Value::Null, }; let result = hook(Some(&vec![cmd]), &ctx, event_tx, &plugins).await; assert!(result.is_err(), "Hook should fail due to timeout"); } #[tokio::test] async fn test_hook_working_dir_applied() { let ctx = create_test_context(); let (event_tx, _event_rx) = create_test_event_channel(); let plugins = PluginMap::new(); let cmd = CustomCommand { name: "pwd-check".to_string(), command: "pwd".to_string(), environment: None, working_dir: Some("/tmp".to_string()), timeout_ms: 5000, argument: serde_yaml::Value::Null, }; // We verify by checking the hook returns Ok — the working_dir is passed to // Engine::execute_command_with_delta. Full stdout verification would require // mocking the Engine or using a more intrusive test setup. let result = hook(Some(&vec![cmd]), &ctx, event_tx, &plugins).await; assert!(result.is_ok(), "Hook with working_dir should succeed"); } #[tokio::test] async fn test_no_hooks_returns_ok() { let ctx = create_test_context(); let (event_tx, _event_rx) = create_test_event_channel(); let plugins = PluginMap::new(); let result = hook(None, &ctx, event_tx, &plugins).await; assert!(result.is_ok(), "No hooks should return Ok immediately"); } #[tokio::test] async fn test_multiple_hooks_execute_sequentially() { let ctx = create_test_context(); let (event_tx, _event_rx) = create_test_event_channel(); let plugins = PluginMap::new(); let cmds = vec![ CustomCommand { name: "hook-1".to_string(), command: "true".to_string(), environment: None, working_dir: None, timeout_ms: 5000, argument: serde_yaml::Value::Null, }, CustomCommand { name: "hook-2".to_string(), command: "true".to_string(), environment: None, working_dir: None, timeout_ms: 5000, argument: serde_yaml::Value::Null, }, ]; let result = hook(Some(&cmds), &ctx, event_tx, &plugins).await; assert!( result.is_ok(), "Multiple hooks should execute sequentially and succeed" ); } #[tokio::test] async fn test_first_failure_stops_remaining_hooks() { let ctx = create_test_context(); let (event_tx, _event_rx) = create_test_event_channel(); let plugins = PluginMap::new(); let cmds = vec![ CustomCommand { name: "failing-hook".to_string(), command: "false".to_string(), environment: None, working_dir: None, timeout_ms: 5000, argument: serde_yaml::Value::Null, }, CustomCommand { name: "never-reached".to_string(), command: "true".to_string(), environment: None, working_dir: None, timeout_ms: 5000, argument: serde_yaml::Value::Null, }, ]; let result = hook(Some(&cmds), &ctx, event_tx, &plugins).await; assert!(result.is_err(), "First failing hook should stop execution"); }