use mockforge_plugin_core::template::{TemplateFunction, TemplatePlugin, TemplatePluginConfig};
use mockforge_plugin_core::*;
use mockforge_plugin_loader::*;
use std::collections::HashMap;
use std::fs;
use tempfile::TempDir;
#[cfg(test)]
mod tests {
use super::*;
#[derive(Debug)]
#[allow(dead_code)]
struct MockTemplatePlugin;
#[::async_trait::async_trait]
impl TemplatePlugin for MockTemplatePlugin {
fn capabilities(&self) -> PluginCapabilities {
PluginCapabilities::default()
}
async fn initialize(&self, _config: &TemplatePluginConfig) -> Result<()> {
Ok(())
}
async fn register_functions(
&self,
_context: &PluginContext,
_config: &TemplatePluginConfig,
) -> Result<PluginResult<HashMap<String, TemplateFunction>>> {
let mut functions = HashMap::new();
functions.insert(
"test_function".to_string(),
TemplateFunction::new("test_function", "string", "Returns a test result"),
);
functions.insert(
"add".to_string(),
TemplateFunction::new("add", "number", "Adds two numbers"),
);
Ok(PluginResult::success(functions, 0))
}
async fn execute_function(
&self,
_context: &PluginContext,
function_name: &str,
args: &[serde_json::Value],
_config: &TemplatePluginConfig,
) -> Result<PluginResult<serde_json::Value>> {
match function_name {
"test_function" => Ok(PluginResult::success(serde_json::json!("test_result"), 0)),
"add" => {
if args.len() == 2 {
if let (Some(a), Some(b)) = (args[0].as_i64(), args[1].as_i64()) {
Ok(PluginResult::success(serde_json::json!(a + b), 0))
} else {
Ok(PluginResult::failure("Arguments must be numbers".to_string(), 0)
as PluginResult<serde_json::Value>)
}
} else {
Ok(
PluginResult::failure(
"Add function requires 2 arguments".to_string(),
0,
) as PluginResult<serde_json::Value>,
)
}
}
_ => Ok(PluginResult::failure(format!("Unknown function: {}", function_name), 0)
as PluginResult<serde_json::Value>),
}
}
async fn get_data_source(
&self,
_context: &PluginContext,
_data_source: &str,
_config: &TemplatePluginConfig,
) -> Result<PluginResult<serde_json::Value>> {
Ok(PluginResult::success(serde_json::json!("test_data"), 0))
}
fn validate_config(&self, _config: &TemplatePluginConfig) -> Result<()> {
Ok(())
}
fn available_data_sources(&self) -> Vec<String> {
vec!["test_data".to_string()]
}
async fn cleanup(&self) -> Result<()> {
Ok(())
}
}
fn create_mock_plugin_manifest() -> PluginManifest {
let id = PluginId::new("mock-template-plugin");
let version = PluginVersion::new(1, 0, 0);
let author = PluginAuthor::with_email("Test Suite", "test@example.com");
let info = PluginInfo::new(
id,
version,
"Mock Template Plugin",
"A mock plugin for testing",
author,
);
PluginManifest::new(info).with_capability("template")
}
fn create_minimal_wasm_module() -> Vec<u8> {
vec![
0x00, 0x61, 0x73, 0x6D, 0x01, 0x00, 0x00, 0x00, 0x01, 0x05, 0x01, 0x60, 0x00, 0x00, 0x03, 0x02, 0x01, 0x00, 0x07, 0x0A, 0x01, 0x06, 0x72, 0x75, 0x6E, 0x00,
0x00, 0x0A, 0x04, 0x01, 0x02, 0x00, 0x0B, ]
}
#[tokio::test]
async fn test_complete_plugin_lifecycle() {
let temp_dir = TempDir::new().unwrap();
let manifest_path = temp_dir.path().join("plugin.yaml");
let wasm_path = temp_dir.path().join("plugin.wasm");
let manifest = create_mock_plugin_manifest();
let yaml_content = serde_yaml::to_string(&manifest).unwrap();
fs::write(&manifest_path, yaml_content).unwrap();
let wasm_bytes = create_minimal_wasm_module();
fs::write(&wasm_path, wasm_bytes).unwrap();
let config = PluginLoaderConfig {
allow_unsigned: true, skip_wasm_validation: true, ..Default::default()
};
let loader = PluginLoader::new(config);
let validation_result = loader.validate_plugin(temp_dir.path()).await;
assert!(validation_result.is_ok(), "Plugin validation should succeed");
let validated_manifest = validation_result.unwrap();
assert_eq!(validated_manifest.info.id.as_str(), "mock-template-plugin");
let plugin_id = PluginId::new("mock-template-plugin".to_string());
let load_result = loader.load_plugin(&plugin_id).await;
assert!(load_result.is_ok() || load_result.is_err());
let _stats = loader.get_load_stats().await;
let unload_result = loader.unload_plugin(&plugin_id).await;
assert!(unload_result.is_ok() || unload_result.is_err()); }
#[tokio::test]
async fn test_plugin_registry_operations() {
let config = PluginLoaderConfig::default();
let loader = PluginLoader::new(config);
let initial_plugins = loader.list_plugins().await;
let _initial_count = initial_plugins.len();
let nonexistent_id = PluginId::new("nonexistent-plugin".to_string());
let plugin = loader.get_plugin(&nonexistent_id).await;
assert!(plugin.is_none());
let health_result = loader.get_plugin_health(&nonexistent_id).await;
assert!(health_result.is_err());
}
#[tokio::test]
async fn test_configuration_handling() {
let temp_dir = TempDir::new().unwrap();
let manifest_path = temp_dir.path().join("plugin.yaml");
let manifest = create_mock_plugin_manifest();
let yaml_content = serde_yaml::to_string(&manifest).unwrap();
fs::write(&manifest_path, yaml_content).unwrap();
let wasm_path = temp_dir.path().join("plugin.wasm");
let wasm_bytes = create_minimal_wasm_module();
fs::write(&wasm_path, wasm_bytes).unwrap();
let result = PluginManifest::from_file(&manifest_path);
assert!(result.is_ok(), "Manifest should parse successfully from YAML");
let parsed_manifest = result.unwrap();
assert!(parsed_manifest.capabilities.contains(&"template".to_string()));
}
#[tokio::test]
async fn test_bulk_plugin_operations() {
let config = PluginLoaderConfig::default();
let loader = PluginLoader::new(config);
let reload_stats = loader.reload_all_plugins().await;
let stats = reload_stats.unwrap();
assert_eq!(stats.discovered, 0); assert_eq!(stats.loaded, 0);
assert_eq!(stats.failed, 0);
assert_eq!(stats.skipped, 0);
assert_eq!(stats.success_rate(), 1.0); }
#[tokio::test]
async fn test_error_handling() {
let config = PluginLoaderConfig::default();
let loader = PluginLoader::new(config);
let nonexistent_id = PluginId::new("does-not-exist".to_string());
let get_result = loader.get_plugin(&nonexistent_id).await;
assert!(get_result.is_none());
let health_result = loader.get_plugin_health(&nonexistent_id).await;
assert!(health_result.is_err());
let _loader_mut = loader; }
#[tokio::test]
async fn test_plugin_context_creation() {
let plugin_id = PluginId::new("test-plugin");
let version = PluginVersion::new(1, 0, 0);
let mut context = PluginContext::new(plugin_id, version)
.with_custom("method", serde_json::json!("POST"))
.with_custom("uri", serde_json::json!("/api/users"))
.with_custom("headers", serde_json::json!({"content-type": "application/json"}))
.with_custom("body", serde_json::json!({"name": "John", "email": "john@example.com"}));
assert_eq!(context.custom.get("method"), Some(&serde_json::json!("POST")));
assert_eq!(context.custom.get("uri"), Some(&serde_json::json!("/api/users")));
assert_eq!(
context.custom.get("headers"),
Some(&serde_json::json!({"content-type": "application/json"}))
);
assert_eq!(
context.custom.get("body"),
Some(&serde_json::json!({"name": "John", "email": "john@example.com"}))
);
context = context.with_custom("user_id", serde_json::json!("user123"));
context = context.with_custom("request_id", serde_json::json!("req456"));
assert_eq!(context.custom.len(), 6); assert_eq!(context.custom.get("user_id"), Some(&serde_json::json!("user123")));
assert_eq!(context.custom.get("request_id"), Some(&serde_json::json!("req456")));
assert_eq!(context.custom.get("nonexistent"), None);
let headers = context.custom.get("headers").unwrap();
assert_eq!(headers.get("content-type"), Some(&serde_json::json!("application/json")));
}
#[tokio::test]
async fn test_capability_validation() {
let config = PluginLoaderConfig::default();
let validator = PluginValidator::new(config);
let valid_capabilities = vec![
"template".to_string(),
"network:http".to_string(),
"filesystem:read".to_string(),
];
let result = validator.validate_capabilities(&valid_capabilities);
assert!(result.is_ok(), "Valid capabilities should pass validation");
let manifest = create_mock_plugin_manifest();
assert!(manifest.capabilities.contains(&"template".to_string()));
let capabilities = PluginCapabilities::from_strings(&valid_capabilities);
assert!(capabilities.custom.contains_key("template"));
assert!(capabilities.network.allow_http);
assert!(capabilities.filesystem.read_paths.contains(&"*".to_string()));
}
#[tokio::test]
async fn test_plugin_result_handling() {
let success_result = PluginResult::success("test data".to_string(), 100);
assert!(success_result.success);
assert_eq!(success_result.data, Some("test data".to_string()));
assert!(success_result.error.is_none());
let failure_result =
PluginResult::failure("test error".to_string(), 150) as PluginResult<String>;
assert!(!failure_result.success);
assert!(failure_result.data.is_none());
assert_eq!(failure_result.error, Some("test error".to_string()));
assert_eq!(failure_result.execution_time_ms, 150);
let mut result_with_metadata = PluginResult::success("data".to_string(), 200);
result_with_metadata
.metadata
.insert("plugin_version".to_string(), serde_json::json!("1.0.0"));
result_with_metadata
.metadata
.insert("execution_mode".to_string(), serde_json::json!("sandboxed"));
assert!(result_with_metadata.success);
assert_eq!(result_with_metadata.metadata.len(), 2);
assert_eq!(
result_with_metadata.metadata.get("plugin_version"),
Some(&serde_json::json!("1.0.0"))
);
}
#[tokio::test]
async fn test_plugin_health_states() {
let metrics = PluginMetrics::default();
let healthy = PluginHealth::healthy("Plugin is running normally".to_string(), metrics);
assert!(healthy.healthy);
assert_eq!(healthy.message, "Plugin is running normally");
let unhealthy_metrics = PluginMetrics::default();
let unhealthy = PluginHealth::unhealthy(
PluginState::Error,
"Plugin crashed".to_string(),
unhealthy_metrics,
);
assert!(!unhealthy.healthy);
assert_eq!(unhealthy.message, "Plugin crashed");
assert_eq!(unhealthy.state, PluginState::Error);
assert!(PluginState::Ready.is_ready());
assert!(!PluginState::Error.is_ready());
assert!(!PluginState::Unloaded.is_ready());
assert!(!PluginState::Loading.is_ready());
}
#[tokio::test]
async fn test_plugin_metrics() {
let mut metrics = PluginMetrics::default();
assert_eq!(metrics.total_executions, 0);
assert_eq!(metrics.avg_execution_time_ms, 0.0);
assert_eq!(metrics.max_execution_time_ms, 0);
assert_eq!(metrics.failed_executions, 0);
metrics.total_executions += 1;
metrics.successful_executions += 1;
let execution_time_1 = 100u64;
metrics.avg_execution_time_ms = execution_time_1 as f64;
metrics.max_execution_time_ms = execution_time_1;
metrics.total_executions += 1;
metrics.successful_executions += 1;
let execution_time_2 = 50u64;
metrics.avg_execution_time_ms = (metrics.avg_execution_time_ms
* (metrics.total_executions - 1) as f64
+ execution_time_2 as f64)
/ metrics.total_executions as f64;
if execution_time_2 > metrics.max_execution_time_ms {
metrics.max_execution_time_ms = execution_time_2;
}
metrics.total_executions += 1;
metrics.failed_executions += 1;
let execution_time_3 = 200u64;
metrics.avg_execution_time_ms = (metrics.avg_execution_time_ms
* (metrics.total_executions - 1) as f64
+ execution_time_3 as f64)
/ metrics.total_executions as f64;
if execution_time_3 > metrics.max_execution_time_ms {
metrics.max_execution_time_ms = execution_time_3;
}
assert_eq!(metrics.total_executions, 3);
assert_eq!(metrics.successful_executions, 2);
assert_eq!(metrics.failed_executions, 1);
assert_eq!(metrics.max_execution_time_ms, 200);
assert!((metrics.avg_execution_time_ms - 116.66666666666667).abs() < 0.001);
}
#[tokio::test]
async fn test_plugin_dependencies() {
let mut dependencies = HashMap::new();
dependencies.insert(PluginId::new("base-plugin"), PluginVersion::new(1, 0, 0));
dependencies.insert(PluginId::new("optional-plugin"), PluginVersion::new(2, 1, 0));
assert_eq!(dependencies.len(), 2);
let required_dep = dependencies.get(&PluginId::new("base-plugin")).unwrap();
assert_eq!(required_dep.to_string(), "1.0.0");
let optional_dep = dependencies.get(&PluginId::new("optional-plugin")).unwrap();
assert_eq!(optional_dep.to_string(), "2.1.0");
}
#[tokio::test]
async fn test_concurrent_plugin_operations() {
let config = PluginLoaderConfig::default();
let _loader = PluginLoader::new(config);
let tasks = (0..10).map(|i| {
let _plugin_id = PluginId::new(format!("test-plugin-{}", i));
tokio::spawn(async move {
})
});
for task in tasks {
let _ = task.await;
}
}
}