diff --git a/crates/app-tauri/src/plugins.rs b/crates/app-tauri/src/plugins.rs index f9a4c21..69f74a0 100644 --- a/crates/app-tauri/src/plugins.rs +++ b/crates/app-tauri/src/plugins.rs @@ -1,5 +1,6 @@ //! Plugin Tauri commands and asset protocol. +use std::future::Future; use std::path::{Path, PathBuf}; use application::{ReviewPluginPackageInput, SetPluginEnabledInput, UninstallPluginInput}; @@ -178,7 +179,7 @@ fn plugin_asset_response( let rel = RelativePath::new(rel.to_owned()).map_err(|e| (StatusCode::BAD_REQUEST, e.to_string()))?; let state = app.state::(); - let allowed = tauri::async_runtime::block_on(asset_allowed( + let allowed = block_on_protocol_future(asset_allowed( &plugin_id, hash, &rel, @@ -255,6 +256,13 @@ async fn asset_allowed( Ok(declared_main || declared_icon || rel.as_str().starts_with("assets/")) } +fn block_on_protocol_future(future: F) -> F::Output { + match tokio::runtime::Handle::try_current() { + Ok(handle) => tokio::task::block_in_place(|| handle.block_on(future)), + Err(_) => tauri::async_runtime::block_on(future), + } +} + fn content_type(path: &Path) -> &'static str { match path .extension() @@ -291,3 +299,15 @@ fn open_folder(path: &PathBuf) -> Result<(), ErrorDto> { .map(|_| ()) .map_err(|e| ErrorDto::invalid(e.to_string())) } + +#[cfg(test)] +mod tests { + use super::block_on_protocol_future; + + #[tokio::test(flavor = "multi_thread")] + async fn protocol_future_can_be_waited_inside_tauri_runtime() { + let value = block_on_protocol_future(async { 42 }); + + assert_eq!(value, 42); + } +} diff --git a/crates/infrastructure/tests/plugin_install_load.rs b/crates/infrastructure/tests/plugin_install_load.rs index f86a976..1623dc6 100644 --- a/crates/infrastructure/tests/plugin_install_load.rs +++ b/crates/infrastructure/tests/plugin_install_load.rs @@ -33,6 +33,12 @@ fn fixture_path(name: &str) -> PathBuf { .join(name) } +fn sdk_hello_plugin_path() -> PathBuf { + Path::new(env!("CARGO_MANIFEST_DIR")) + .join("../..") + .join("sdk/IdeaSDK/examples/hello-plugin") +} + #[tokio::test] async fn installs_reference_fixture_and_loads_runtime_catalog() { let app_data = temp_dir("app-data"); @@ -90,3 +96,53 @@ async fn installs_reference_fixture_and_loads_runtime_catalog() { let _ = fs::remove_dir_all(app_data); } + +#[tokio::test] +async fn installs_sdk_hello_plugin_and_loads_runtime_catalog() { + let app_data = temp_dir("hello-app-data"); + let hello_plugin = sdk_hello_plugin_path(); + let packages = Arc::new(FsPluginPackageStore::new(&app_data)); + let registry = Arc::new(FsPluginRegistryStore::new(&app_data)); + let validator = Arc::new(JsonPluginManifestValidator::new("0.3.0")); + let events = Arc::new(TokioBroadcastEventBus::new()); + let mcp = Arc::new(ExternalMcpPluginSupervisor::new()); + + let install = InstallPluginFromDirectory::new( + packages.clone(), + registry.clone(), + validator.clone(), + events, + mcp, + ); + let result = install + .execute(hello_plugin.to_string_lossy().into_owned()) + .await + .unwrap(); + + assert_eq!(result.plugin.id, "com.example.hello-plugin"); + assert_eq!(result.plugin.display_name, "Hello Plugin"); + assert_eq!(result.review.contribution_summary.top_level_menus, 1); + assert_eq!(result.review.contribution_summary.menu_items, 1); + assert!(app_data + .join("plugins/installed/com.example.hello-plugin/dist/index.js") + .is_file()); + + let catalog = ListPluginRuntimeContributions::new(packages, registry, validator) + .execute() + .await + .unwrap(); + assert_eq!(catalog.plugins.len(), 1); + let plugin = &catalog.plugins[0]; + assert_eq!(plugin.id, "com.example.hello-plugin"); + assert!(plugin + .bundle_url + .starts_with("idea-plugin://com.example.hello-plugin/0.1.0/")); + assert!(plugin.bundle_url.ends_with("/dist/index.js")); + assert_eq!(plugin.contributes.menus[0].id, "hello-plugin.menu"); + assert_eq!( + plugin.contributes.menu_items[0].command.as_str(), + "hello-plugin.sayHello" + ); + + let _ = fs::remove_dir_all(app_data); +}