mockforge-plugin-loader 0.3.147

Plugin loader with security sandboxing and validation for MockForge
Documentation
//! Comprehensive tests for the plugin loader functionality

use mockforge_plugin_core::*;
use mockforge_plugin_loader::*;
use std::fs;
use tempfile::TempDir;

#[cfg(test)]
mod tests {
    use super::*;

    fn create_test_plugin_manifest() -> PluginManifest {
        let id = PluginId::new("test-plugin");
        let version = PluginVersion::new(1, 0, 0);
        let author = PluginAuthor::with_email("Test Author", "test@example.com");
        let info = PluginInfo::new(id, version, "Test Plugin", "A test plugin", author);

        PluginManifest::new(info).with_capability("template")
    }

    fn create_test_wasm_bytes() -> Vec<u8> {
        // Create a minimal valid WASM module
        // This is a simplified WASM binary that just contains a valid header
        vec![
            0x00, 0x61, 0x73, 0x6D, // WASM magic number
            0x01, 0x00, 0x00,
            0x00, // WASM version
                  // Empty sections - this is a minimal valid module
        ]
    }

    #[tokio::test]
    async fn test_plugin_loader_initialization() {
        let config = PluginLoaderConfig::default();
        let _loader = PluginLoader::new(config);

        // Verify loader is initialized
        // Note: plugins list may be empty initially
    }

    #[tokio::test]
    async fn test_plugin_validation_success() {
        let temp_dir = TempDir::new().unwrap();
        let manifest_path = temp_dir.path().join("plugin.yaml");
        let wasm_path = temp_dir.path().join("plugin.wasm");

        // Create test manifest
        let manifest = create_test_plugin_manifest();
        let yaml_content = serde_yaml::to_string(&manifest).unwrap();
        fs::write(&manifest_path, yaml_content).unwrap();

        // Create test WASM file
        let wasm_bytes = create_test_wasm_bytes();
        fs::write(&wasm_path, wasm_bytes).unwrap();

        let config = PluginLoaderConfig {
            skip_wasm_validation: true, // Skip WASM validation for test
            ..Default::default()
        };
        let loader = PluginLoader::new(config);

        // Test validation
        let result = loader.validate_plugin(temp_dir.path()).await;
        assert!(result.is_ok(), "Plugin validation should succeed");

        let validated_manifest = result.unwrap();
        assert_eq!(validated_manifest.info.id.as_str(), "test-plugin");
        assert_eq!(validated_manifest.info.version.to_string(), "1.0.0");
    }

    #[tokio::test]
    async fn test_plugin_validation_missing_manifest() {
        let temp_dir = TempDir::new().unwrap();

        let config = PluginLoaderConfig::default();
        let loader = PluginLoader::new(config);

        // Test validation with missing manifest
        let result = loader.validate_plugin(temp_dir.path()).await;
        assert!(result.is_err(), "Plugin validation should fail with missing manifest");
    }

    #[tokio::test]
    async fn test_plugin_validation_invalid_manifest() {
        let temp_dir = TempDir::new().unwrap();
        let manifest_path = temp_dir.path().join("plugin.yaml");

        // Create invalid YAML
        fs::write(&manifest_path, "invalid: yaml: content: [unclosed").unwrap();

        let config = PluginLoaderConfig::default();
        let loader = PluginLoader::new(config);

        // Test validation with invalid manifest
        let result = loader.validate_plugin(temp_dir.path()).await;
        assert!(result.is_err(), "Plugin validation should fail with invalid manifest");
    }

    #[tokio::test]
    async fn test_plugin_validation_missing_wasm() {
        let temp_dir = TempDir::new().unwrap();
        let manifest_path = temp_dir.path().join("plugin.yaml");

        // Create valid manifest but no WASM file
        let manifest = create_test_plugin_manifest();
        let yaml_content = serde_yaml::to_string(&manifest).unwrap();
        fs::write(&manifest_path, yaml_content).unwrap();

        let config = PluginLoaderConfig::default();
        let loader = PluginLoader::new(config);

        // Test validation with missing WASM
        let result = loader.validate_plugin(temp_dir.path()).await;
        assert!(result.is_err(), "Plugin validation should fail with missing WASM");
    }

    #[tokio::test]
    async fn test_plugin_loading_and_unloading() {
        let config = PluginLoaderConfig::default();
        let loader = PluginLoader::new(config);

        let plugin_id = PluginId::new("test-plugin".to_string());
        let _manifest = create_test_plugin_manifest();
        let _wasm_bytes = create_test_wasm_bytes();

        // Test loading (this would normally fail due to WASM complexity, but tests the API)
        let result = loader.load_plugin(&plugin_id).await;
        // Loading a synthetic WASM module is expected to fail in tests
        assert!(
            result.is_err(),
            "expected plugin load to fail in test environment, but got: {result:?}"
        );

        // Unloading a plugin that was never loaded returns NotFound
        let unload_result = loader.unload_plugin(&plugin_id).await;
        assert!(
            unload_result.is_err(),
            "expected NotFound error when unloading a plugin that was never loaded"
        );
    }

    #[tokio::test]
    async fn test_plugin_health_check() {
        let config = PluginLoaderConfig::default();
        let loader = PluginLoader::new(config);

        let plugin_id = PluginId::new("nonexistent-plugin".to_string());

        // Test health check for non-existent plugin
        let result = loader.get_plugin_health(&plugin_id).await;
        assert!(result.is_err(), "Health check should fail for non-existent plugin");
    }

    #[tokio::test]
    async fn test_plugin_list_and_get() {
        let config = PluginLoaderConfig::default();
        let loader = PluginLoader::new(config);

        // Test listing plugins
        let plugins = loader.list_plugins().await;
        assert!(plugins.is_empty() || !plugins.is_empty()); // Either is acceptable

        // Test getting non-existent plugin
        let plugin_id = PluginId::new("nonexistent".to_string());
        let result = loader.get_plugin(&plugin_id).await;
        assert!(result.is_none(), "Getting non-existent plugin should return None");
    }

    #[tokio::test]
    async fn test_plugin_reload_operations() {
        let config = PluginLoaderConfig::default();
        let loader = PluginLoader::new(config);

        // Test reload all plugins
        let stats = loader.reload_all_plugins().await;
        let stats = stats.unwrap();
        assert_eq!(stats.discovered, 0); // Should be 0 in test environment
        assert_eq!(stats.loaded, 0);
        assert_eq!(stats.failed, 0);

        // Test reload specific plugin
        let plugin_id = PluginId::new("test-plugin".to_string());
        let result = loader.reload_plugin(&plugin_id).await;
        assert!(result.is_err(), "Reloading non-existent plugin should fail");
    }

    #[tokio::test]
    async fn test_load_statistics() {
        let config = PluginLoaderConfig::default();
        let loader = PluginLoader::new(config);

        // Test getting load statistics
        let stats = loader.get_load_stats().await;
        assert_eq!(stats.total_plugins(), 0); // Should be 0 in test environment
        assert!(stats.success_rate() >= 0.0 && stats.success_rate() <= 1.0);
    }

    #[tokio::test]
    async fn test_security_validation() {
        let temp_dir = TempDir::new().unwrap();
        let manifest_path = temp_dir.path().join("plugin.yaml");
        let wasm_path = temp_dir.path().join("plugin.wasm");

        // Create manifest with excessive resource requirements
        let manifest = create_test_plugin_manifest();
        let yaml_content = serde_yaml::to_string(&manifest).unwrap();
        fs::write(&manifest_path, yaml_content).unwrap();

        // Create test WASM file
        let wasm_bytes = create_test_wasm_bytes();
        fs::write(&wasm_path, wasm_bytes).unwrap();

        let config = PluginLoaderConfig {
            skip_wasm_validation: true, // Skip WASM validation for test
            ..Default::default()
        };
        let loader = PluginLoader::new(config);

        // Test validation
        let result = loader.validate_plugin(temp_dir.path()).await;
        assert!(result.is_ok(), "Plugin validation should pass even with high resource limits");
    }

    #[tokio::test]
    async fn test_network_capability_validation() {
        let temp_dir = TempDir::new().unwrap();
        let manifest_path = temp_dir.path().join("plugin.yaml");
        let wasm_path = temp_dir.path().join("plugin.wasm");

        // Create manifest with network access
        let manifest = create_test_plugin_manifest();
        let yaml_content = serde_yaml::to_string(&manifest).unwrap();
        fs::write(&manifest_path, yaml_content).unwrap();

        // Create test WASM file
        let wasm_bytes = create_test_wasm_bytes();
        fs::write(&wasm_path, wasm_bytes).unwrap();

        let config = PluginLoaderConfig {
            skip_wasm_validation: true, // Skip WASM validation for test
            ..Default::default()
        };
        let loader = PluginLoader::new(config);

        // Test validation
        let result = loader.validate_plugin(temp_dir.path()).await;
        assert!(result.is_ok(), "Plugin validation should pass with network capabilities");

        let validated_manifest = result.unwrap();
        assert!(validated_manifest.capabilities.contains(&"template".to_string()));
    }

    #[tokio::test]
    async fn test_filesystem_capability_validation() {
        let temp_dir = TempDir::new().unwrap();
        let manifest_path = temp_dir.path().join("plugin.yaml");
        let wasm_path = temp_dir.path().join("plugin.wasm");

        // Create manifest with filesystem access
        let manifest = create_test_plugin_manifest();
        let yaml_content = serde_yaml::to_string(&manifest).unwrap();
        fs::write(&manifest_path, yaml_content).unwrap();

        // Create test WASM file
        let wasm_bytes = create_test_wasm_bytes();
        fs::write(&wasm_path, wasm_bytes).unwrap();

        let config = PluginLoaderConfig {
            skip_wasm_validation: true, // Skip WASM validation for test
            ..Default::default()
        };
        let loader = PluginLoader::new(config);

        // Test validation
        let result = loader.validate_plugin(temp_dir.path()).await;
        assert!(result.is_ok(), "Plugin validation should pass with filesystem capabilities");

        let validated_manifest = result.unwrap();
        assert!(validated_manifest.capabilities.contains(&"template".to_string()));
    }

    #[tokio::test]
    async fn test_dependency_validation() {
        let temp_dir = TempDir::new().unwrap();
        let manifest_path = temp_dir.path().join("plugin.yaml");
        let wasm_path = temp_dir.path().join("plugin.wasm");

        // Create manifest with dependencies
        let manifest = create_test_plugin_manifest();
        let yaml_content = serde_yaml::to_string(&manifest).unwrap();
        fs::write(&manifest_path, yaml_content).unwrap();

        // Create test WASM file
        let wasm_bytes = create_test_wasm_bytes();
        fs::write(&wasm_path, wasm_bytes).unwrap();

        let config = PluginLoaderConfig {
            skip_wasm_validation: true, // Skip WASM validation for test
            ..Default::default()
        };
        let loader = PluginLoader::new(config);

        // Test validation
        let result = loader.validate_plugin(temp_dir.path()).await;
        assert!(result.is_ok(), "Plugin validation should pass with dependencies");

        let validated_manifest = result.unwrap();
        assert!(validated_manifest.dependencies.is_empty());
    }

    #[tokio::test]
    async fn test_plugin_capability_validation() {
        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_test_plugin_manifest();
        let yaml_content = serde_yaml::to_string(&manifest).unwrap();
        fs::write(&manifest_path, yaml_content).unwrap();

        // Create test WASM file
        let wasm_bytes = create_test_wasm_bytes();
        fs::write(&wasm_path, wasm_bytes).unwrap();

        let config = PluginLoaderConfig {
            skip_wasm_validation: true, // Skip WASM validation for test
            ..Default::default()
        };
        let loader = PluginLoader::new(config);

        let result = loader.validate_plugin(temp_dir.path()).await;
        assert!(result.is_ok(), "Plugin validation should pass");

        let validated_manifest = result.unwrap();
        assert!(
            validated_manifest.capabilities.contains(&"template".to_string()),
            "Plugin should have template capability"
        );
    }

    #[tokio::test]
    async fn test_configuration_schema_validation() {
        let temp_dir = TempDir::new().unwrap();
        let manifest_path = temp_dir.path().join("plugin.yaml");
        let wasm_path = temp_dir.path().join("plugin.wasm");

        // Create manifest with configuration schema
        let manifest = create_test_plugin_manifest();
        let yaml_content = serde_yaml::to_string(&manifest).unwrap();
        fs::write(&manifest_path, yaml_content).unwrap();

        // Create test WASM file
        let wasm_bytes = create_test_wasm_bytes();
        fs::write(&wasm_path, wasm_bytes).unwrap();

        let config = PluginLoaderConfig {
            skip_wasm_validation: true, // Skip WASM validation for test
            ..Default::default()
        };
        let loader = PluginLoader::new(config);

        // Test validation
        let result = loader.validate_plugin(temp_dir.path()).await;
        assert!(result.is_ok(), "Plugin validation should pass with configuration schema");

        let validated_manifest = result.unwrap();
        assert!(validated_manifest.dependencies.is_empty());
    }
}