#[cfg(test)]
mod tests {
use crate::core::{
discovery::{PluginDiscovery, PluginFactory},
registry::PluginRegistry,
Plugin, PluginContext, PluginMetadata, PluginOutput, PluginResult,
};
use async_trait::async_trait;
use serde_json::json;
use std::sync::{Arc, Mutex};
#[derive(Default)]
struct DiscoveryTestPluginA {
metadata: PluginMetadata,
call_count: Arc<Mutex<usize>>,
}
impl DiscoveryTestPluginA {
fn new() -> Self {
Self {
metadata: PluginMetadata::new("discovery-test-a", "1.0.0"),
call_count: Arc::new(Mutex::new(0)),
}
}
#[allow(dead_code)]
fn get_call_count(&self) -> usize {
*self.call_count.lock().unwrap()
}
}
#[async_trait]
impl Plugin for DiscoveryTestPluginA {
fn metadata(&self) -> &PluginMetadata {
&self.metadata
}
fn schema(&self) -> serde_json::Value {
json!({"type": "object"})
}
async fn initialize(
&mut self,
_config: serde_json::Value,
_context: &PluginContext,
) -> PluginResult<()> {
*self.call_count.lock().unwrap() += 1;
Ok(())
}
async fn execute(&mut self, _context: &mut PluginContext) -> PluginResult<PluginOutput> {
*self.call_count.lock().unwrap() += 1;
Ok(PluginOutput::success(
json!({"plugin": "test-a", "executed": true}),
))
}
async fn cleanup(&mut self, _context: &PluginContext) -> PluginResult<()> {
*self.call_count.lock().unwrap() += 1;
Ok(())
}
}
#[derive(Default)]
#[allow(dead_code)]
struct DiscoveryTestPluginB {
metadata: PluginMetadata,
}
impl DiscoveryTestPluginB {
#[allow(dead_code)]
fn new() -> Self {
let mut metadata = PluginMetadata::new("discovery-test-b", "2.0.0");
metadata.dependencies = vec!["discovery-test-a".to_string()];
Self { metadata }
}
}
#[async_trait]
impl Plugin for DiscoveryTestPluginB {
fn metadata(&self) -> &PluginMetadata {
&self.metadata
}
fn schema(&self) -> serde_json::Value {
json!({"type": "object"})
}
async fn initialize(
&mut self,
_config: serde_json::Value,
_context: &PluginContext,
) -> PluginResult<()> {
Ok(())
}
async fn execute(&mut self, _context: &mut PluginContext) -> PluginResult<PluginOutput> {
Ok(PluginOutput::success(
json!({"plugin": "test-b", "executed": true}),
))
}
async fn cleanup(&mut self, _context: &PluginContext) -> PluginResult<()> {
Ok(())
}
}
#[test]
fn test_plugin_factory_creation_and_usage() {
let factory = PluginFactory::new(
"test-factory",
|| Box::new(DiscoveryTestPluginA::new()),
|| DiscoveryTestPluginA::new().metadata().clone(),
"1.0.0",
"Test factory plugin",
);
assert_eq!(factory.name, "test-factory");
assert_eq!(factory.version, "1.0.0");
assert_eq!(factory.description, "Test factory plugin");
let plugin = factory.create_plugin();
assert_eq!(plugin.metadata().name, "discovery-test-a");
assert_eq!(plugin.metadata().version, "1.0.0");
let metadata = factory.get_metadata();
assert_eq!(metadata.name, "discovery-test-a");
assert_eq!(metadata.version, "1.0.0");
}
#[test]
fn test_plugin_discovery_factory_management() {
let discovery = PluginDiscovery::new();
assert_eq!(discovery.get_all_factories().len(), 0);
assert!(!discovery.has_plugin("test"));
assert!(discovery.list_plugin_names().is_empty());
let stats = discovery.get_stats();
assert_eq!(stats.total_factories, 0);
assert!(!stats.cached);
}
#[test]
fn test_plugin_discovery_search_paths() {
let mut discovery = PluginDiscovery::new();
discovery.add_search_path("/test/plugins".into());
discovery.add_search_path("/usr/local/plugins".into());
let search_paths = discovery.search_paths();
assert_eq!(search_paths.len(), 2);
assert_eq!(search_paths[0].to_str().unwrap(), "/test/plugins");
assert_eq!(search_paths[1].to_str().unwrap(), "/usr/local/plugins");
let stats = discovery.get_stats();
assert_eq!(stats.search_paths, 2);
}
#[test]
fn test_plugin_discovery_cache_behavior() {
let mut discovery = PluginDiscovery::new();
assert!(!discovery.get_stats().cached);
discovery.clear_cache();
assert!(!discovery.get_stats().cached);
let _factories = discovery.discover_plugins().unwrap();
assert!(discovery.get_stats().cached);
discovery.clear_cache();
assert!(!discovery.get_stats().cached);
}
#[test]
fn test_plugin_discovery_error_handling() {
let discovery = PluginDiscovery::new();
let result = discovery.create_plugin("nonexistent");
assert!(result.is_err());
if let Err(error) = result {
assert!(matches!(error, crate::core::PluginError::PluginNotFound(_)));
}
let result = discovery.get_plugin_metadata("nonexistent");
assert!(result.is_err());
if let Err(error) = result {
assert!(matches!(error, crate::core::PluginError::PluginNotFound(_)));
}
assert!(discovery.get_factory("nonexistent").is_none());
}
#[test]
fn test_registry_discovery_integration() {
let mut registry = PluginRegistry::new();
assert_eq!(registry.len(), 0);
assert!(registry.is_empty());
let plugin = DiscoveryTestPluginA::new();
registry.register(plugin).unwrap();
assert_eq!(registry.len(), 1);
assert!(!registry.is_empty());
}
#[test]
fn test_registry_auto_discovery() {
let mut registry = PluginRegistry::new();
let result = registry.auto_discover();
assert!(result.is_ok());
assert_eq!(result.unwrap(), 0);
}
#[test]
fn test_registry_manual_discovery_integration() {
let mut registry = PluginRegistry::new();
let discovery = PluginDiscovery::new();
let plugin_names = vec!["nonexistent".to_string()];
let result = registry.register_from_discovery(&discovery, &plugin_names);
assert!(result.is_ok());
assert_eq!(result.unwrap(), 0);
assert_eq!(registry.len(), 0);
}
#[test]
fn test_plugin_factory_validation() {
let discovery = PluginDiscovery::new();
let valid_factory = PluginFactory::new(
"valid-plugin",
|| Box::new(DiscoveryTestPluginA::new()),
|| DiscoveryTestPluginA::new().metadata().clone(),
"1.0.0",
"Valid test plugin",
);
let validation_result = discovery.validate_factory(&valid_factory);
assert!(validation_result.is_ok());
let invalid_factory = PluginFactory::new(
"",
|| Box::new(DiscoveryTestPluginA::new()),
|| DiscoveryTestPluginA::new().metadata().clone(),
"1.0.0",
"Invalid plugin with empty name",
);
let validation_result = discovery.validate_factory(&invalid_factory);
assert!(validation_result.is_err());
let invalid_factory2 = PluginFactory::new(
"invalid-plugin",
|| Box::new(DiscoveryTestPluginA::new()),
|| DiscoveryTestPluginA::new().metadata().clone(),
"",
"Invalid plugin with empty version",
);
let validation_result = discovery.validate_factory(&invalid_factory2);
assert!(validation_result.is_err());
}
#[test]
fn test_discovery_stats_comprehensive() {
let mut discovery = PluginDiscovery::new();
let stats = discovery.get_stats();
assert_eq!(stats.total_factories, 0);
assert_eq!(stats.search_paths, 0);
assert!(!stats.cached);
discovery.add_search_path("/test1".into());
discovery.add_search_path("/test2".into());
let stats = discovery.get_stats();
assert_eq!(stats.search_paths, 2);
assert!(!stats.cached);
let _result = discovery.discover_plugins();
let stats = discovery.get_stats();
assert!(stats.cached);
}
#[test]
fn test_plugin_cloning_and_multiple_instances() {
let factory = PluginFactory::new(
"clonable-plugin",
|| Box::new(DiscoveryTestPluginA::new()),
|| DiscoveryTestPluginA::new().metadata().clone(),
"1.0.0",
"Clonable test plugin",
);
let plugin1 = factory.create_plugin();
let plugin2 = factory.create_plugin();
assert_eq!(plugin1.metadata().name, plugin2.metadata().name);
assert_eq!(plugin1.metadata().version, plugin2.metadata().version);
let ptr1 = plugin1.as_ref() as *const dyn Plugin;
let ptr2 = plugin2.as_ref() as *const dyn Plugin;
assert_ne!(ptr1, ptr2);
}
}