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:
2026-08-01 19:11:54 +02:00
parent 0061685b08
commit 5baf5821d4
2 changed files with 148 additions and 11 deletions

View File

@ -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"]);
}
}