test(backend,plugins): ajoute un test de non-résidu runtime après uninstall/reinstall (#120)
- plugin_install_load.rs: test uninstalls_then_reinstalls_sdk_hello_plugin_without_runtime_residue() - application/src/plugin/mod.rs: refactor FakePackages pour supporter plusieurs staged packages et nettoyer les manifests sur uninstall - Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@ -1360,7 +1360,7 @@ fn parse_version_tuple(raw: &str) -> Option<(u64, u64, u64)> {
|
||||
mod tests {
|
||||
use super::*;
|
||||
use domain::ports::{EventStream, PluginPackageStore, PluginRegistryStore, PluginStoreError};
|
||||
use std::collections::HashMap;
|
||||
use std::collections::{HashMap, VecDeque};
|
||||
use std::sync::Mutex;
|
||||
|
||||
fn validator() -> JsonPluginManifestValidator {
|
||||
@ -1397,23 +1397,32 @@ mod tests {
|
||||
|
||||
struct FakePackages {
|
||||
manifests: Mutex<HashMap<String, Vec<u8>>>,
|
||||
staged: Mutex<Option<StagedPluginPackage>>,
|
||||
staged_manifest: Vec<u8>,
|
||||
staged: Mutex<VecDeque<StagedPluginPackage>>,
|
||||
removed: Mutex<Vec<String>>,
|
||||
}
|
||||
|
||||
impl FakePackages {
|
||||
fn with_manifest(bytes: Vec<u8>) -> Self {
|
||||
Self::with_manifest_and_staged_count(bytes, 1)
|
||||
}
|
||||
|
||||
fn with_manifest_and_staged_count(bytes: Vec<u8>, staged_count: usize) -> Self {
|
||||
let mut manifests = HashMap::new();
|
||||
manifests.insert("dev.acme.gitgraph".to_owned(), bytes);
|
||||
Self {
|
||||
manifests: Mutex::new(manifests),
|
||||
staged: Mutex::new(Some(StagedPluginPackage {
|
||||
root: "/stage/plugin".to_owned(),
|
||||
let staged = (0..staged_count)
|
||||
.map(|index| StagedPluginPackage {
|
||||
root: format!("/stage/plugin-{index}"),
|
||||
source: PluginInstallSource::Directory {
|
||||
path_label: "/source/plugin".to_owned(),
|
||||
},
|
||||
content_hash: content_hash("abc123"),
|
||||
})),
|
||||
})
|
||||
.collect();
|
||||
Self {
|
||||
manifests: Mutex::new(manifests),
|
||||
staged_manifest: valid_manifest(),
|
||||
staged: Mutex::new(staged),
|
||||
removed: Mutex::new(Vec::new()),
|
||||
}
|
||||
}
|
||||
@ -1442,6 +1451,11 @@ mod tests {
|
||||
.plugin_id
|
||||
.as_ref()
|
||||
.map_or("dev.acme.gitgraph", PluginId::as_str);
|
||||
if package.plugin_id.is_none() {
|
||||
return Ok(PluginManifestBytes {
|
||||
bytes: self.staged_manifest.clone(),
|
||||
});
|
||||
}
|
||||
self.manifests
|
||||
.lock()
|
||||
.unwrap()
|
||||
@ -1458,7 +1472,7 @@ mod tests {
|
||||
self.staged
|
||||
.lock()
|
||||
.unwrap()
|
||||
.take()
|
||||
.pop_front()
|
||||
.ok_or_else(|| PluginStoreError::Invalid("missing staged package".to_owned()))
|
||||
}
|
||||
|
||||
@ -1469,7 +1483,7 @@ mod tests {
|
||||
self.staged
|
||||
.lock()
|
||||
.unwrap()
|
||||
.take()
|
||||
.pop_front()
|
||||
.ok_or_else(|| PluginStoreError::Invalid("missing staged package".to_owned()))
|
||||
}
|
||||
|
||||
@ -1481,7 +1495,7 @@ mod tests {
|
||||
self.manifests
|
||||
.lock()
|
||||
.unwrap()
|
||||
.insert(plugin_id.as_str().to_owned(), valid_manifest());
|
||||
.insert(plugin_id.as_str().to_owned(), self.staged_manifest.clone());
|
||||
Ok(domain::PluginPackageRef {
|
||||
plugin_id: Some(plugin_id.clone()),
|
||||
root: staged.root,
|
||||
@ -1496,6 +1510,7 @@ mod tests {
|
||||
.lock()
|
||||
.unwrap()
|
||||
.push(plugin_id.as_str().to_owned());
|
||||
self.manifests.lock().unwrap().remove(plugin_id.as_str());
|
||||
Ok(RemovalOutcome::Removed)
|
||||
}
|
||||
|
||||
@ -1910,4 +1925,53 @@ mod tests {
|
||||
}
|
||||
)));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn uninstall_then_reinstall_leaves_runtime_catalog_active_without_residue() {
|
||||
let packages = Arc::new(FakePackages::with_manifest_and_staged_count(
|
||||
valid_manifest(),
|
||||
2,
|
||||
));
|
||||
let registry = Arc::new(FakeRegistry::default());
|
||||
let events = Arc::new(FakeEvents::default());
|
||||
let mcp = Arc::new(FakeMcp::default());
|
||||
let install = InstallPluginFromDirectory::new(
|
||||
packages.clone(),
|
||||
registry.clone(),
|
||||
Arc::new(validator()),
|
||||
events.clone(),
|
||||
mcp.clone(),
|
||||
);
|
||||
let uninstall =
|
||||
UninstallPlugin::new(packages.clone(), registry.clone(), events, mcp.clone());
|
||||
|
||||
install.execute("/source/plugin".to_owned()).await.unwrap();
|
||||
uninstall
|
||||
.execute(UninstallPluginInput {
|
||||
plugin_id: "dev.acme.gitgraph".to_owned(),
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
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();
|
||||
|
||||
assert_eq!(result.plugin.lifecycle_state, PluginLifecycleState::Enabled);
|
||||
assert_eq!(runtime.plugins.len(), 1);
|
||||
assert_eq!(runtime.plugins[0].id, "dev.acme.gitgraph");
|
||||
let saved = registry.load_registry().await.unwrap();
|
||||
let entry = saved.find(&plugin_id()).unwrap();
|
||||
assert_eq!(entry.lifecycle_state, PluginLifecycleState::Enabled);
|
||||
assert!(entry.error.is_none());
|
||||
assert_eq!(&*packages.removed.lock().unwrap(), &["dev.acme.gitgraph"]);
|
||||
assert_eq!(&*mcp.stops.lock().unwrap(), &["dev.acme.gitgraph"]);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user