feat(application,backend,infrastructure,app-tauri,frontend): contributions UI/runtime des plugins limitées aux projets liés — #321 (QA verte)
Les contributions runtime des plugins (menus, slash commands, layouts, surfaces) ne sont exposées que dans les projets liés au plugin. - Définition du lien: ProjectPluginSettingsStore par racine projet (domaine) — une contribution runtime-active n'entre dans le catalogue que si elle est explicitement activée pour le projet courant; hors projet focalisé, catalogue vide - Application: ListPluginRuntimeContributions.execute prend un project_id optionnel, charge projet + settings et filtre le registre par activation projet; tests fake store/settings (catalogue vide sans projet, activation, layout, slash commands) - Backend/app-tauri: plugin_list_runtime_contributions et slash commands list/execute acceptent projectId optionnel (DTO camelCase, défaut serde), BackendCore recâblé avec store_port et project_plugin_settings_store; test DTO de scope optionnel - Frontend: PluginGateway.listRuntimeContributions(projectId) (ports, tauri, http/unsupported, mock alignés); PluginRuntimeProvider re-scope le catalogue au changement de focus projet (vidage du registre précédent, disposition des bundles, requêtes obsolètes ignorées par projectId/révision); PluginLayoutCellView ignore les références de layout devenues hors périmètre via onChooseAnotherLayout et ne rend rien pendant le rechargement (aucune fuite inter-projets) - Tests: plugin_install_load (infrastructure), dto slash commands, plugin.test, PluginRuntimeProvider.test, PluginLayoutCellView.test QA verte sur feature/321-plugin-contributions-linked-projects. Aucun push.
This commit is contained in:
@ -125,9 +125,14 @@ pub async fn list_slash_commands(
|
||||
state: State<'_, AppState>,
|
||||
) -> Result<SlashCommandListDto, ErrorDto> {
|
||||
let request = request.unwrap_or_default();
|
||||
let project_id = request
|
||||
.project_id
|
||||
.as_deref()
|
||||
.map(parse_project_id)
|
||||
.transpose()?;
|
||||
let catalog = state
|
||||
.list_plugin_runtime_contributions
|
||||
.execute()
|
||||
.execute(project_id)
|
||||
.await
|
||||
.map_err(ErrorDto::from)?;
|
||||
let plugin_commands =
|
||||
@ -153,6 +158,11 @@ pub async fn execute_slash_command(
|
||||
request: ExecuteSlashCommandRequestDto,
|
||||
state: State<'_, AppState>,
|
||||
) -> Result<ExecuteSlashCommandResponseDto, ErrorDto> {
|
||||
let project_id = request
|
||||
.project_id
|
||||
.as_deref()
|
||||
.map(parse_project_id)
|
||||
.transpose()?;
|
||||
let session_id = request
|
||||
.session_id
|
||||
.as_deref()
|
||||
@ -160,7 +170,7 @@ pub async fn execute_slash_command(
|
||||
.transpose()?;
|
||||
let catalog = state
|
||||
.list_plugin_runtime_contributions
|
||||
.execute()
|
||||
.execute(project_id)
|
||||
.await
|
||||
.map_err(ErrorDto::from)?;
|
||||
let plugin_commands =
|
||||
|
||||
@ -283,11 +283,15 @@ pub async fn plugin_uninstall(
|
||||
/// Lists active runtime contributions for frontend bootstrap.
|
||||
#[tauri::command]
|
||||
pub async fn plugin_list_runtime_contributions(
|
||||
project_id: Option<String>,
|
||||
state: State<'_, AppState>,
|
||||
) -> Result<PluginRuntimeContributionCatalogDto, ErrorDto> {
|
||||
let project_id = project_id
|
||||
.map(|project_id| parse_project_id(&project_id))
|
||||
.transpose()?;
|
||||
state
|
||||
.list_plugin_runtime_contributions
|
||||
.execute()
|
||||
.execute(project_id)
|
||||
.await
|
||||
.map(PluginRuntimeContributionCatalogDto::from)
|
||||
.map_err(ErrorDto::from)
|
||||
|
||||
@ -15,8 +15,8 @@ use domain::{
|
||||
AgentId, BackgroundTask, BackgroundTaskState, BackgroundTaskWakePolicy, ContentHash,
|
||||
DomainEvent, PluginContributionSet, PluginDescriptor, PluginId, PluginInstallSource,
|
||||
PluginLayoutType, PluginLifecycleState, PluginManifest, PluginMcpServerSpec,
|
||||
PluginRegistryEntry, PluginTrustLevel, Project, ProjectId, ProjectPath, RemovalOutcome,
|
||||
StagedPluginPackage, TaskId,
|
||||
PluginRegistryEntry, PluginTrustLevel, Project, ProjectId, ProjectPath,
|
||||
ProjectPluginSettingsStore, RemovalOutcome, StagedPluginPackage, TaskId,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use uuid::Uuid;
|
||||
@ -2948,6 +2948,8 @@ pub struct ListPluginRuntimeContributions {
|
||||
packages: Arc<dyn PluginPackageStore>,
|
||||
registry: Arc<dyn PluginRegistryStore>,
|
||||
validator: Arc<dyn PluginManifestValidator>,
|
||||
projects: Arc<dyn ProjectStore>,
|
||||
project_settings: Arc<dyn ProjectPluginSettingsStore>,
|
||||
}
|
||||
|
||||
impl ListPluginRuntimeContributions {
|
||||
@ -2957,21 +2959,45 @@ impl ListPluginRuntimeContributions {
|
||||
packages: Arc<dyn PluginPackageStore>,
|
||||
registry: Arc<dyn PluginRegistryStore>,
|
||||
validator: Arc<dyn PluginManifestValidator>,
|
||||
projects: Arc<dyn ProjectStore>,
|
||||
project_settings: Arc<dyn ProjectPluginSettingsStore>,
|
||||
) -> Self {
|
||||
Self {
|
||||
packages,
|
||||
registry,
|
||||
validator,
|
||||
projects,
|
||||
project_settings,
|
||||
}
|
||||
}
|
||||
|
||||
/// Executes the use case.
|
||||
pub async fn execute(&self) -> Result<PluginRuntimeCatalog, AppError> {
|
||||
///
|
||||
/// Without a project scope, no project-visible contribution is exposed.
|
||||
pub async fn execute(
|
||||
&self,
|
||||
project_id: Option<ProjectId>,
|
||||
) -> Result<PluginRuntimeCatalog, AppError> {
|
||||
let Some(project_id) = project_id else {
|
||||
return Ok(PluginRuntimeCatalog {
|
||||
plugins: Vec::new(),
|
||||
});
|
||||
};
|
||||
let project = self
|
||||
.projects
|
||||
.load_project(project_id)
|
||||
.await
|
||||
.map_err(AppError::from)?;
|
||||
let settings = self
|
||||
.project_settings
|
||||
.load(&project.root)
|
||||
.await
|
||||
.map_err(AppError::from)?;
|
||||
let mut registry = self.registry.load_registry().await.map_err(map_registry)?;
|
||||
let mut plugins = Vec::new();
|
||||
let mut invalid_plugins = Vec::new();
|
||||
for entry in registry.plugins.clone() {
|
||||
if !entry.lifecycle_state.is_runtime_active() {
|
||||
if !entry.lifecycle_state.is_runtime_active() || !settings.is_enabled(&entry.id) {
|
||||
continue;
|
||||
}
|
||||
match runtime_plugin_from_entry(
|
||||
@ -3797,6 +3823,60 @@ mod tests {
|
||||
PluginId::new("dev.acme.gitgraph").unwrap()
|
||||
}
|
||||
|
||||
fn runtime_project_id() -> ProjectId {
|
||||
ProjectId::from_uuid(Uuid::from_u128(0x321))
|
||||
}
|
||||
|
||||
fn runtime_project() -> Project {
|
||||
Project::new(
|
||||
runtime_project_id(),
|
||||
"Runtime project",
|
||||
ProjectPath::new("/runtime-project").unwrap(),
|
||||
RemoteRef::Local,
|
||||
0,
|
||||
)
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
fn runtime_usecase(
|
||||
packages: Arc<dyn PluginPackageStore>,
|
||||
registry: Arc<dyn PluginRegistryStore>,
|
||||
validator: Arc<dyn PluginManifestValidator>,
|
||||
) -> ListPluginRuntimeContributions {
|
||||
runtime_usecase_with_settings(
|
||||
packages,
|
||||
registry,
|
||||
validator,
|
||||
domain::ProjectPluginSettings {
|
||||
plugins: vec![domain::ProjectPluginActivation {
|
||||
plugin_id: plugin_id(),
|
||||
enabled: true,
|
||||
}],
|
||||
..domain::ProjectPluginSettings::default()
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
fn runtime_usecase_with_settings(
|
||||
packages: Arc<dyn PluginPackageStore>,
|
||||
registry: Arc<dyn PluginRegistryStore>,
|
||||
validator: Arc<dyn PluginManifestValidator>,
|
||||
settings: domain::ProjectPluginSettings,
|
||||
) -> ListPluginRuntimeContributions {
|
||||
let project = runtime_project();
|
||||
let projects = Arc::new(FakeProjectStore {
|
||||
projects: Mutex::new(HashMap::from([(project.id, project)])),
|
||||
});
|
||||
let project_settings = Arc::new(FakeRuntimeProjectSettings { settings });
|
||||
ListPluginRuntimeContributions::new(
|
||||
packages,
|
||||
registry,
|
||||
validator,
|
||||
projects,
|
||||
project_settings,
|
||||
)
|
||||
}
|
||||
|
||||
fn content_hash(raw: &str) -> ContentHash {
|
||||
ContentHash::new(raw).unwrap()
|
||||
}
|
||||
@ -4296,25 +4376,61 @@ mod tests {
|
||||
let registry = Arc::new(FakeRegistry {
|
||||
registry: Mutex::new(registry_with(state)),
|
||||
});
|
||||
let usecase =
|
||||
ListPluginRuntimeContributions::new(packages, registry, Arc::new(validator()));
|
||||
let usecase = runtime_usecase(packages, registry, Arc::new(validator()));
|
||||
|
||||
let catalog = usecase.execute().await.unwrap();
|
||||
let catalog = usecase.execute(Some(runtime_project_id())).await.unwrap();
|
||||
|
||||
assert!(catalog.plugins.is_empty(), "{state:?} must not be active");
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn runtime_catalog_without_project_scope_exposes_no_contributions() {
|
||||
let packages = Arc::new(FakePackages::with_manifest(valid_manifest()));
|
||||
let registry = Arc::new(FakeRegistry {
|
||||
registry: Mutex::new(registry_with(PluginLifecycleState::Enabled)),
|
||||
});
|
||||
let usecase = runtime_usecase(packages, registry, Arc::new(validator()));
|
||||
|
||||
let catalog = usecase.execute(None).await.unwrap();
|
||||
|
||||
assert!(catalog.plugins.is_empty());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn runtime_catalog_requires_explicit_project_activation_not_agent_assignment() {
|
||||
let packages = Arc::new(FakePackages::with_manifest(valid_manifest()));
|
||||
let registry = Arc::new(FakeRegistry {
|
||||
registry: Mutex::new(registry_with(PluginLifecycleState::Enabled)),
|
||||
});
|
||||
let settings = domain::ProjectPluginSettings {
|
||||
agents: vec![domain::AgentPluginAssignments {
|
||||
agent_id: AgentId::from_uuid(Uuid::from_u128(7)),
|
||||
skills: vec![domain::PluginSkillRef {
|
||||
plugin_id: plugin_id(),
|
||||
skill_id: domain::PluginSkillId::new("build-debug").unwrap(),
|
||||
}],
|
||||
tools: Vec::new(),
|
||||
}],
|
||||
..domain::ProjectPluginSettings::default()
|
||||
};
|
||||
let usecase =
|
||||
runtime_usecase_with_settings(packages, registry, Arc::new(validator()), settings);
|
||||
|
||||
let catalog = usecase.execute(Some(runtime_project_id())).await.unwrap();
|
||||
|
||||
assert!(catalog.plugins.is_empty());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn runtime_catalog_carries_manifest_capabilities() {
|
||||
let packages = Arc::new(FakePackages::with_manifest(valid_manifest()));
|
||||
let registry = Arc::new(FakeRegistry {
|
||||
registry: Mutex::new(registry_with(PluginLifecycleState::Enabled)),
|
||||
});
|
||||
let usecase =
|
||||
ListPluginRuntimeContributions::new(packages, registry, Arc::new(validator()));
|
||||
let usecase = runtime_usecase(packages, registry, Arc::new(validator()));
|
||||
|
||||
let catalog = usecase.execute().await.unwrap();
|
||||
let catalog = usecase.execute(Some(runtime_project_id())).await.unwrap();
|
||||
|
||||
assert_eq!(catalog.plugins.len(), 1);
|
||||
assert_eq!(
|
||||
@ -4338,10 +4454,9 @@ mod tests {
|
||||
let registry = Arc::new(FakeRegistry {
|
||||
registry: Mutex::new(registry_with(PluginLifecycleState::Enabled)),
|
||||
});
|
||||
let usecase =
|
||||
ListPluginRuntimeContributions::new(packages, registry.clone(), Arc::new(validator()));
|
||||
let usecase = runtime_usecase(packages, registry.clone(), Arc::new(validator()));
|
||||
|
||||
let catalog = usecase.execute().await.unwrap();
|
||||
let catalog = usecase.execute(Some(runtime_project_id())).await.unwrap();
|
||||
|
||||
assert!(
|
||||
catalog.plugins.is_empty(),
|
||||
@ -4673,11 +4788,10 @@ mod tests {
|
||||
}
|
||||
)));
|
||||
|
||||
let runtime =
|
||||
ListPluginRuntimeContributions::new(packages, registry, Arc::new(validator()))
|
||||
.execute()
|
||||
.await
|
||||
.unwrap();
|
||||
let runtime = runtime_usecase(packages, registry, Arc::new(validator()))
|
||||
.execute(Some(runtime_project_id()))
|
||||
.await
|
||||
.unwrap();
|
||||
assert!(runtime.plugins.is_empty());
|
||||
}
|
||||
|
||||
@ -4838,14 +4952,10 @@ mod tests {
|
||||
assert!(registry.load_registry().await.unwrap().plugins.is_empty());
|
||||
|
||||
let result = install.execute("/source/plugin".to_owned()).await.unwrap();
|
||||
let runtime = ListPluginRuntimeContributions::new(
|
||||
packages.clone(),
|
||||
registry.clone(),
|
||||
Arc::new(validator()),
|
||||
)
|
||||
.execute()
|
||||
.await
|
||||
.unwrap();
|
||||
let runtime = runtime_usecase(packages.clone(), registry.clone(), Arc::new(validator()))
|
||||
.execute(Some(runtime_project_id()))
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(result.plugin.lifecycle_state, PluginLifecycleState::Enabled);
|
||||
assert_eq!(runtime.plugins.len(), 1);
|
||||
@ -4957,6 +5067,28 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
struct FakeRuntimeProjectSettings {
|
||||
settings: domain::ProjectPluginSettings,
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl ProjectPluginSettingsStore for FakeRuntimeProjectSettings {
|
||||
async fn load(
|
||||
&self,
|
||||
_project_root: &ProjectPath,
|
||||
) -> Result<domain::ProjectPluginSettings, StoreError> {
|
||||
Ok(self.settings.clone())
|
||||
}
|
||||
|
||||
async fn save(
|
||||
&self,
|
||||
_project_root: &ProjectPath,
|
||||
_settings: &domain::ProjectPluginSettings,
|
||||
) -> Result<(), StoreError> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct FakeWorkspaceFs {
|
||||
files: Mutex<HashMap<String, Vec<u8>>>,
|
||||
|
||||
@ -917,6 +917,9 @@ impl From<HealthReport> for HealthResponseDto {
|
||||
#[derive(Debug, Clone, Default, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ListSlashCommandsRequestDto {
|
||||
/// Project whose explicitly enabled plugin contributions may be listed.
|
||||
#[serde(default)]
|
||||
pub project_id: Option<String>,
|
||||
/// Optional typed prefix, with or without the leading `/`.
|
||||
#[serde(default)]
|
||||
pub prefix: Option<String>,
|
||||
@ -945,6 +948,9 @@ impl From<ListSlashCommandsOutput> for SlashCommandListDto {
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ExecuteSlashCommandRequestDto {
|
||||
/// Project whose explicitly enabled plugin contribution may be executed.
|
||||
#[serde(default)]
|
||||
pub project_id: Option<String>,
|
||||
/// Command name, with or without the leading `/`.
|
||||
pub name: String,
|
||||
/// Current structured chat session id, required by `/clean` and future
|
||||
@ -6251,6 +6257,28 @@ mod tests {
|
||||
use serde_json::json;
|
||||
use uuid::Uuid;
|
||||
|
||||
#[test]
|
||||
fn slash_command_requests_accept_optional_project_scope() {
|
||||
let project_id = Uuid::from_u128(321).to_string();
|
||||
let list: ListSlashCommandsRequestDto = serde_json::from_value(json!({
|
||||
"projectId": project_id,
|
||||
"prefix": "/graph"
|
||||
}))
|
||||
.unwrap();
|
||||
assert_eq!(list.project_id.as_deref(), Some(project_id.as_str()));
|
||||
|
||||
let execute: ExecuteSlashCommandRequestDto = serde_json::from_value(json!({
|
||||
"projectId": project_id,
|
||||
"name": "/graph",
|
||||
"arguments": []
|
||||
}))
|
||||
.unwrap();
|
||||
assert_eq!(execute.project_id.as_deref(), Some(project_id.as_str()));
|
||||
|
||||
let unscoped: ListSlashCommandsRequestDto = serde_json::from_value(json!({})).unwrap();
|
||||
assert!(unscoped.project_id.is_none());
|
||||
}
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
|
||||
@ -3831,6 +3831,8 @@ impl BackendCore {
|
||||
Arc::clone(&plugin_package_store),
|
||||
Arc::clone(&plugin_registry_store),
|
||||
Arc::clone(&plugin_manifest_validator),
|
||||
Arc::clone(&store_port),
|
||||
Arc::clone(&project_plugin_settings_store),
|
||||
));
|
||||
let reconcile_plugin_mcp_servers = Arc::new(ReconcilePluginMcpServers::new(
|
||||
Arc::clone(&plugin_package_store),
|
||||
|
||||
@ -7,6 +7,12 @@ use application::{
|
||||
InstallPluginFromDirectory, JsonPluginManifestValidator, ListPluginRuntimeContributions,
|
||||
ListPlugins, UninstallPlugin, UninstallPluginInput,
|
||||
};
|
||||
use async_trait::async_trait;
|
||||
use domain::{
|
||||
PluginId, PluginManifestValidator, PluginPackageStore, PluginRegistryStore, Project, ProjectId,
|
||||
ProjectPath, ProjectPluginActivation, ProjectPluginSettings, ProjectPluginSettingsStore,
|
||||
ProjectStore, RemoteRef, StoreError,
|
||||
};
|
||||
use infrastructure::{
|
||||
ExternalMcpPluginSupervisor, FsPluginPackageStore, FsPluginRegistryStore, FsPluginStorageStore,
|
||||
TokioBroadcastEventBus,
|
||||
@ -14,6 +20,88 @@ use infrastructure::{
|
||||
|
||||
static TEMP_COUNTER: AtomicU64 = AtomicU64::new(0);
|
||||
|
||||
struct RuntimeProjectStore {
|
||||
project: Project,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl ProjectStore for RuntimeProjectStore {
|
||||
async fn list_projects(&self) -> Result<Vec<Project>, StoreError> {
|
||||
Ok(vec![self.project.clone()])
|
||||
}
|
||||
|
||||
async fn load_project(&self, id: ProjectId) -> Result<Project, StoreError> {
|
||||
(id == self.project.id)
|
||||
.then(|| self.project.clone())
|
||||
.ok_or(StoreError::NotFound)
|
||||
}
|
||||
|
||||
async fn save_project(&self, _project: &Project) -> Result<(), StoreError> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn save_workspace(&self, _workspace: &domain::Workspace) -> Result<(), StoreError> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn load_workspace(&self) -> Result<domain::Workspace, StoreError> {
|
||||
Ok(domain::Workspace::default())
|
||||
}
|
||||
}
|
||||
|
||||
struct RuntimeProjectSettings {
|
||||
settings: ProjectPluginSettings,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl ProjectPluginSettingsStore for RuntimeProjectSettings {
|
||||
async fn load(&self, _project_root: &ProjectPath) -> Result<ProjectPluginSettings, StoreError> {
|
||||
Ok(self.settings.clone())
|
||||
}
|
||||
|
||||
async fn save(
|
||||
&self,
|
||||
_project_root: &ProjectPath,
|
||||
_settings: &ProjectPluginSettings,
|
||||
) -> Result<(), StoreError> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn runtime_catalog_usecase(
|
||||
packages: Arc<dyn PluginPackageStore>,
|
||||
registry: Arc<dyn PluginRegistryStore>,
|
||||
validator: Arc<dyn PluginManifestValidator>,
|
||||
plugin_id: &str,
|
||||
) -> (ListPluginRuntimeContributions, ProjectId) {
|
||||
let project_id = ProjectId::new_random();
|
||||
let project = Project::new(
|
||||
project_id,
|
||||
"Runtime project",
|
||||
ProjectPath::new("/runtime-project").unwrap(),
|
||||
RemoteRef::Local,
|
||||
0,
|
||||
)
|
||||
.unwrap();
|
||||
let settings = ProjectPluginSettings {
|
||||
plugins: vec![ProjectPluginActivation {
|
||||
plugin_id: PluginId::new(plugin_id).unwrap(),
|
||||
enabled: true,
|
||||
}],
|
||||
..ProjectPluginSettings::default()
|
||||
};
|
||||
(
|
||||
ListPluginRuntimeContributions::new(
|
||||
packages,
|
||||
registry,
|
||||
validator,
|
||||
Arc::new(RuntimeProjectStore { project }),
|
||||
Arc::new(RuntimeProjectSettings { settings }),
|
||||
),
|
||||
project_id,
|
||||
)
|
||||
}
|
||||
|
||||
fn temp_dir(label: &str) -> PathBuf {
|
||||
let n = TEMP_COUNTER.fetch_add(1, Ordering::SeqCst);
|
||||
let path = std::env::temp_dir().join(format!(
|
||||
@ -124,10 +212,13 @@ async fn installs_reference_fixture_and_loads_runtime_catalog() {
|
||||
domain::PluginLifecycleState::Enabled
|
||||
);
|
||||
|
||||
let catalog = ListPluginRuntimeContributions::new(packages, registry, validator)
|
||||
.execute()
|
||||
.await
|
||||
.unwrap();
|
||||
let (runtime, project_id) = runtime_catalog_usecase(
|
||||
packages,
|
||||
registry,
|
||||
validator,
|
||||
"dev.idea.fixtures.reference-minimal",
|
||||
);
|
||||
let catalog = runtime.execute(Some(project_id)).await.unwrap();
|
||||
assert_eq!(catalog.plugins.len(), 1);
|
||||
let plugin = &catalog.plugins[0];
|
||||
assert_eq!(plugin.id, "dev.idea.fixtures.reference-minimal");
|
||||
@ -179,10 +270,9 @@ async fn installs_sdk_hello_plugin_and_loads_runtime_catalog() {
|
||||
.join("plugins/installed/com.example.hello-plugin/dist/index.js")
|
||||
.is_file());
|
||||
|
||||
let catalog = ListPluginRuntimeContributions::new(packages, registry, validator)
|
||||
.execute()
|
||||
.await
|
||||
.unwrap();
|
||||
let (runtime, project_id) =
|
||||
runtime_catalog_usecase(packages, registry, validator, "com.example.hello-plugin");
|
||||
let catalog = runtime.execute(Some(project_id)).await.unwrap();
|
||||
assert_eq!(catalog.plugins.len(), 1);
|
||||
let plugin = &catalog.plugins[0];
|
||||
assert_eq!(plugin.id, "com.example.hello-plugin");
|
||||
@ -261,16 +351,18 @@ async fn uninstalls_then_reinstalls_sdk_hello_plugin_without_runtime_residue() {
|
||||
.unwrap()
|
||||
.is_empty()
|
||||
);
|
||||
assert!(ListPluginRuntimeContributions::new(
|
||||
let (runtime, project_id) = runtime_catalog_usecase(
|
||||
packages.clone(),
|
||||
registry.clone(),
|
||||
validator.clone()
|
||||
)
|
||||
.execute()
|
||||
.await
|
||||
.unwrap()
|
||||
.plugins
|
||||
.is_empty());
|
||||
validator.clone(),
|
||||
"com.example.hello-plugin",
|
||||
);
|
||||
assert!(runtime
|
||||
.execute(Some(project_id))
|
||||
.await
|
||||
.unwrap()
|
||||
.plugins
|
||||
.is_empty());
|
||||
|
||||
let reinstall = install
|
||||
.execute(hello_plugin.to_string_lossy().into_owned())
|
||||
@ -281,10 +373,9 @@ async fn uninstalls_then_reinstalls_sdk_hello_plugin_without_runtime_residue() {
|
||||
reinstall.plugin.lifecycle_state,
|
||||
domain::PluginLifecycleState::Enabled
|
||||
);
|
||||
let catalog = ListPluginRuntimeContributions::new(packages, registry, validator)
|
||||
.execute()
|
||||
.await
|
||||
.unwrap();
|
||||
let (runtime, project_id) =
|
||||
runtime_catalog_usecase(packages, registry, validator, "com.example.hello-plugin");
|
||||
let catalog = runtime.execute(Some(project_id)).await.unwrap();
|
||||
assert_eq!(catalog.plugins.len(), 1);
|
||||
assert_eq!(catalog.plugins[0].id, "com.example.hello-plugin");
|
||||
assert!(catalog.plugins[0].bundle_url.ends_with("/dist/index.js"));
|
||||
@ -349,14 +440,14 @@ async fn uninstall_multifile_plugin_removes_package_registry_and_runtime_residue
|
||||
.unwrap()
|
||||
.is_empty()
|
||||
);
|
||||
assert!(
|
||||
ListPluginRuntimeContributions::new(packages, registry, validator)
|
||||
.execute()
|
||||
.await
|
||||
.unwrap()
|
||||
.plugins
|
||||
.is_empty()
|
||||
);
|
||||
let (runtime, project_id) =
|
||||
runtime_catalog_usecase(packages, registry, validator, "dev.acme.multifile");
|
||||
assert!(runtime
|
||||
.execute(Some(project_id))
|
||||
.await
|
||||
.unwrap()
|
||||
.plugins
|
||||
.is_empty());
|
||||
|
||||
let _ = fs::remove_dir_all(app_data);
|
||||
let _ = fs::remove_dir_all(source);
|
||||
@ -409,10 +500,13 @@ async fn installed_plugin_with_missing_main_is_isolated_from_runtime_catalog() {
|
||||
.unwrap_or_default()
|
||||
.contains("not servable"));
|
||||
|
||||
let catalog = ListPluginRuntimeContributions::new(packages, registry.clone(), validator)
|
||||
.execute()
|
||||
.await
|
||||
.unwrap();
|
||||
let (runtime, project_id) = runtime_catalog_usecase(
|
||||
packages,
|
||||
registry.clone(),
|
||||
validator,
|
||||
"dev.idea.fixtures.missing-main",
|
||||
);
|
||||
let catalog = runtime.execute(Some(project_id)).await.unwrap();
|
||||
assert!(catalog.plugins.is_empty());
|
||||
let admin = ListPlugins::new(
|
||||
Arc::new(FsPluginPackageStore::new(&app_data)),
|
||||
|
||||
Reference in New Issue
Block a user