#[cfg(test)]
mod tests {
use crate::core::{
config::PluginSystemConfig,
events::{system_events, Event, EventBus, EventHandler, EventPriority},
executor::PluginExecutor,
plugin::{Plugin, PluginContext, PluginMetadata, PluginOutput},
registry::PluginRegistry,
security::Permission,
PluginError, PluginResult,
};
use async_trait::async_trait;
use serde_json::json;
use std::{
path::PathBuf,
sync::{Arc, Mutex},
time::Duration,
};
struct HookTestPlugin {
metadata: PluginMetadata,
hook_calls: Arc<Mutex<Vec<String>>>,
event_received: Arc<Mutex<Vec<Event>>>,
should_fail: bool,
fail_on_hook: Option<String>,
}
impl HookTestPlugin {
fn new(name: &str) -> Self {
Self {
metadata: PluginMetadata::new(name, "1.0.0"),
hook_calls: Arc::new(Mutex::new(Vec::new())),
event_received: Arc::new(Mutex::new(Vec::new())),
should_fail: false,
fail_on_hook: None,
}
}
fn with_failure(mut self, fail_on_hook: Option<String>) -> Self {
self.should_fail = true;
self.fail_on_hook = fail_on_hook;
self
}
#[allow(dead_code)]
fn get_hook_calls(&self) -> Vec<String> {
self.hook_calls.lock().unwrap().clone()
}
#[allow(dead_code)]
fn get_received_events(&self) -> Vec<Event> {
self.event_received.lock().unwrap().clone()
}
fn record_hook(&self, hook_name: &str) -> PluginResult<()> {
self.hook_calls.lock().unwrap().push(hook_name.to_string());
if let Some(ref fail_hook) = self.fail_on_hook {
if hook_name == fail_hook {
return Err(PluginError::InitializationFailed(format!(
"Intentional failure on {hook_name}"
)));
}
}
Ok(())
}
}
#[async_trait]
impl Plugin for HookTestPlugin {
fn metadata(&self) -> &PluginMetadata {
&self.metadata
}
fn schema(&self) -> serde_json::Value {
json!({"type": "object"})
}
fn permissions(&self) -> Vec<Permission> {
vec![Permission::TempDir]
}
fn subscribed_events(&self) -> Vec<String> {
vec![
"test.event".to_string(),
system_events::PLUGIN_EXECUTION_STARTED.to_string(),
system_events::PLUGIN_EXECUTION_COMPLETED.to_string(),
]
}
async fn initialize(
&mut self,
_config: serde_json::Value,
_context: &PluginContext,
) -> PluginResult<()> {
self.record_hook("initialize")?;
if self.should_fail && self.fail_on_hook.is_none() {
return Err(PluginError::InitializationFailed(
"Test failure".to_string(),
));
}
Ok(())
}
async fn execute(&mut self, context: &mut PluginContext) -> PluginResult<PluginOutput> {
self.record_hook("execute")?;
if let Err(e) = context
.emit_event("test.event", json!({"from": self.metadata.name}))
.await
{
eprintln!("Failed to emit test event: {e}");
}
if self.should_fail && self.fail_on_hook.is_none() {
return Err(PluginError::ExecutionFailed("Test failure".to_string()));
}
Ok(PluginOutput::success(json!({"executed": true})))
}
async fn cleanup(&mut self, _context: &PluginContext) -> PluginResult<()> {
self.record_hook("cleanup")?;
Ok(())
}
async fn before_initialize(&mut self, _context: &PluginContext) -> PluginResult<()> {
self.record_hook("before_initialize")
}
async fn after_initialize(&mut self, _context: &PluginContext) -> PluginResult<()> {
self.record_hook("after_initialize")
}
async fn before_execute(&mut self, _context: &PluginContext) -> PluginResult<()> {
self.record_hook("before_execute")
}
async fn after_execute(
&mut self,
_context: &PluginContext,
_output: &PluginOutput,
) -> PluginResult<()> {
self.record_hook("after_execute")
}
async fn before_cleanup(&mut self, _context: &PluginContext) -> PluginResult<()> {
self.record_hook("before_cleanup")
}
async fn after_cleanup(&mut self, _context: &PluginContext) -> PluginResult<()> {
self.record_hook("after_cleanup")
}
async fn on_error(
&mut self,
_context: &PluginContext,
_error: &PluginError,
) -> PluginResult<()> {
self.record_hook("on_error")
}
async fn on_success(
&mut self,
_context: &PluginContext,
_output: &PluginOutput,
) -> PluginResult<()> {
self.record_hook("on_success")
}
async fn on_event(&mut self, _context: &PluginContext, event: &Event) -> PluginResult<()> {
self.event_received.lock().unwrap().push(event.clone());
Ok(())
}
}
struct TestEventHandler {
events_received: Arc<Mutex<Vec<Event>>>,
event_types: Vec<String>,
priority: EventPriority,
}
impl TestEventHandler {
fn new(event_types: Vec<String>, priority: EventPriority) -> Self {
Self {
events_received: Arc::new(Mutex::new(Vec::new())),
event_types,
priority,
}
}
fn get_received_events(&self) -> Vec<Event> {
self.events_received.lock().unwrap().clone()
}
}
#[async_trait]
impl EventHandler for TestEventHandler {
async fn handle_event(&self, event: &Event) -> PluginResult<()> {
self.events_received.lock().unwrap().push(event.clone());
Ok(())
}
fn event_types(&self) -> Vec<String> {
self.event_types.clone()
}
fn priority(&self) -> EventPriority {
self.priority
}
}
async fn create_test_executor() -> PluginExecutor {
let _workspace = PathBuf::from("/tmp/test");
let registry = PluginRegistry::new();
let config = PluginSystemConfig::default();
PluginExecutor::with_config(registry, config)
}
#[tokio::test]
async fn test_plugin_lifecycle_hooks_success() {
let mut executor = create_test_executor().await;
let plugin = HookTestPlugin::new("test-plugin");
let hook_calls_ref = plugin.hook_calls.clone();
executor.registry_mut().register(plugin).unwrap();
let result = executor.execute_pipeline().await;
assert!(result.is_ok());
let execution_result = result.unwrap();
assert!(execution_result.success);
assert_eq!(execution_result.plugin_outputs.len(), 1);
assert!(execution_result.plugin_outputs.contains_key("test-plugin"));
let hook_calls = hook_calls_ref.lock().unwrap().clone();
let expected_hooks = vec![
"before_initialize",
"initialize",
"after_initialize",
"before_execute",
"execute",
"after_execute",
"on_success",
"before_cleanup",
"cleanup",
"after_cleanup",
];
assert_eq!(hook_calls, expected_hooks);
}
#[tokio::test]
async fn test_plugin_lifecycle_hooks_initialization_failure() {
let mut executor = create_test_executor().await;
let plugin =
HookTestPlugin::new("test-plugin").with_failure(Some("initialize".to_string()));
let hook_calls_ref = plugin.hook_calls.clone();
executor.registry_mut().register(plugin).unwrap();
let result = executor.execute_pipeline().await;
assert!(result.is_ok()); let execution_result = result.unwrap();
assert!(!execution_result.success);
assert!(!execution_result.failed_plugins.is_empty());
let hook_calls = hook_calls_ref.lock().unwrap().clone();
assert!(hook_calls.contains(&"before_initialize".to_string()));
assert!(hook_calls.contains(&"initialize".to_string()));
assert!(hook_calls.contains(&"on_error".to_string()));
assert!(!hook_calls.contains(&"after_initialize".to_string()));
assert!(!hook_calls.contains(&"execute".to_string()));
}
#[tokio::test]
async fn test_event_system_integration() {
let event_bus = EventBus::new();
let _receiver = event_bus.subscribe();
let handler = Arc::new(TestEventHandler::new(
vec![
"test.event".to_string(),
system_events::PLUGIN_EXECUTION_STARTED.to_string(),
],
EventPriority::Normal,
));
event_bus.register_handler(handler.clone());
let event1 = Event::new("test.event", json!({"data": "test1"}));
let event2 = system_events::plugin_execution_started("test-plugin");
event_bus.publish(event1).await.unwrap();
event_bus.publish(event2).await.unwrap();
tokio::time::sleep(Duration::from_millis(10)).await;
let received_events = handler.get_received_events();
assert_eq!(received_events.len(), 2);
let event_types: Vec<_> = received_events.iter().map(|e| &e.event_type).collect();
assert!(event_types.contains(&&"test.event".to_string()));
assert!(event_types.contains(&&system_events::PLUGIN_EXECUTION_STARTED.to_string()));
}
#[tokio::test]
async fn test_executor_event_bus_access() {
let executor = create_test_executor().await;
let event_bus = executor.event_bus();
let _receiver = event_bus.subscribe();
let test_event = Event::new("executor.test", json!({"test": true}));
let result = event_bus.publish(test_event).await;
assert!(result.is_ok());
let history = event_bus.get_history();
assert!(!history.is_empty());
assert_eq!(history.last().unwrap().event_type, "executor.test");
}
#[tokio::test]
async fn test_system_event_helpers() {
let plugin_name = "test-plugin";
let duration = Duration::from_millis(100);
let started_event = system_events::plugin_execution_started(plugin_name);
assert_eq!(
started_event.event_type,
system_events::PLUGIN_EXECUTION_STARTED
);
assert_eq!(started_event.source, Some(plugin_name.to_string()));
let completed_event = system_events::plugin_execution_completed(plugin_name, duration);
assert_eq!(
completed_event.event_type,
system_events::PLUGIN_EXECUTION_COMPLETED
);
assert_eq!(completed_event.source, Some(plugin_name.to_string()));
assert_eq!(completed_event.data["duration_ms"], 100);
let error = PluginError::ExecutionFailed("test error".to_string());
let failed_event = system_events::plugin_execution_failed(plugin_name, &error);
assert_eq!(
failed_event.event_type,
system_events::PLUGIN_EXECUTION_FAILED
);
assert_eq!(failed_event.priority, EventPriority::High);
}
#[tokio::test]
async fn test_event_handler_priority_ordering() {
let event_bus = EventBus::new();
let call_order = Arc::new(Mutex::new(Vec::new()));
let _receiver = event_bus.subscribe();
struct OrderTestHandler {
name: String,
priority: EventPriority,
call_order: Arc<Mutex<Vec<String>>>,
}
#[async_trait]
impl EventHandler for OrderTestHandler {
async fn handle_event(&self, _event: &Event) -> PluginResult<()> {
self.call_order.lock().unwrap().push(self.name.clone());
Ok(())
}
fn event_types(&self) -> Vec<String> {
vec!["test".to_string()]
}
fn priority(&self) -> EventPriority {
self.priority
}
}
let handler_low = Arc::new(OrderTestHandler {
name: "low".to_string(),
priority: EventPriority::Low,
call_order: call_order.clone(),
});
let handler_high = Arc::new(OrderTestHandler {
name: "high".to_string(),
priority: EventPriority::High,
call_order: call_order.clone(),
});
let handler_normal = Arc::new(OrderTestHandler {
name: "normal".to_string(),
priority: EventPriority::Normal,
call_order: call_order.clone(),
});
event_bus.register_handler(handler_low);
event_bus.register_handler(handler_high);
event_bus.register_handler(handler_normal);
let event = Event::new("test", json!({}));
event_bus.publish(event).await.unwrap();
tokio::time::sleep(Duration::from_millis(10)).await;
let order = call_order.lock().unwrap().clone();
assert_eq!(order, vec!["high", "normal", "low"]);
}
}