Merge pull request #6679 from dotCooCoo/fix/plugin-grants-delete-test-portable

Plug-ins: the failed delete of grants.json is tested on every platform
This commit is contained in:
BFlat
2026-11-12 13:18:04 +07:00
committed by GitHub
2 changed files with 41 additions and 1 deletions
+12 -1
View File
@@ -191,6 +191,9 @@ fn lock_disabled(_: &Path) -> Option<std::fs::File> {
thread_local! {
/// Tests: [`Session::put_back_plugin`] fails on this thread, as on a full volume.
pub(crate) static PUT_BACK_FAILS: std::cell::Cell<bool> = const { std::cell::Cell::new(false) };
/// Tests: removing [`GRANTS_FILE`] fails on this thread while the file still reads, as when
/// another program holds it open.
pub(crate) static GRANTS_DELETE_FAILS: std::cell::Cell<bool> = const { std::cell::Cell::new(false) };
/// Tests: [`Session::put_back_plugin`] writes the old module back on this thread and then
/// reports a failure, as when the last flush of a copy fails.
pub(crate) static PUT_BACK_FAILS_AFTER_WRITE: std::cell::Cell<bool> = const { std::cell::Cell::new(false) };
@@ -205,7 +208,15 @@ thread_local! {
/// Remove `dir/file` → whether it is gone; a file that isn't there is what was wanted.
fn take(dir: &Path, file: &str) -> bool {
let path = dir.join(file);
match std::fs::remove_file(&path) {
#[cfg(test)]
let removed = if file == GRANTS_FILE && GRANTS_DELETE_FAILS.get() {
Err(std::io::Error::from(std::io::ErrorKind::PermissionDenied))
} else {
std::fs::remove_file(&path)
};
#[cfg(not(test))]
let removed = std::fs::remove_file(&path);
match removed {
Err(e) if e.kind() != std::io::ErrorKind::NotFound => {
log::warn!("can't remove {}: {e}", path.display());
false
+29
View File
@@ -2123,6 +2123,35 @@ fn a_grants_file_that_cannot_be_deleted_keeps_the_stored_module() {
run(&mut s, "plugin.remove", json!({"id": id}));
}
/// When the last grant can't be removed because `grants.json` can't be deleted, a replacing module
/// that asks for no permissions doesn't stay in the store: the store gets back the module the grant
/// still covers, and the grant is left as it was.
#[test]
fn a_grants_file_whose_deletion_fails_keeps_the_stored_module() {
let folder = crate::tests_links::Folder::new("plugin-store-undeletable-grants");
let id = "test.engine.permissions.undeletablegrants";
let empty = json!({"objects": []});
let approved = asking(id, r#"["doc.read"]"#, &empty);
let approved_data = vectorcraft_format::base64_encode(&approved);
let plain = asking(id, "null", &empty);
let grants = folder.0.join("grants.json");
let mut s = session();
s.attach_plugin_store(folder.0.clone());
run(&mut s, "plugin.install", json!({"dataBase64": approved_data, "grant": ["doc.read"]}));
let file = s.stored_plugin_file(id).unwrap();
let recorded = std::fs::read(&grants).unwrap();
crate::store::GRANTS_DELETE_FAILS.set(true);
run(&mut s, "plugin.install", json!({"dataBase64": vectorcraft_format::base64_encode(&plain), "replace": true}));
crate::store::GRANTS_DELETE_FAILS.set(false);
assert_eq!(std::fs::read(&file).unwrap(), approved, "the store gets the module the grant covers back");
assert_eq!(std::fs::read(&grants).unwrap(), recorded, "the grant stays as it was");
assert_eq!(run(&mut s, "plugin.inspect", json!({"dataBase64": approved_data}))["needsApproval"], false, "the grant still covers the module");
run(&mut s, "plugin.remove", json!({"id": id}));
assert!(!grants.exists(), "a removal that can delete the file forgets the grant");
}
/// A plug-in folder that leaves a module listing no permissions under an id forgets the process's
/// grant for it, unless a later file in the folder leaves a granted module there. A grant kept in
/// the plug-in store stays, with the stored module it covers.