Merge main into fix/plugin-grants-delete-test-portable

This commit is contained in:
dotCooCoo
2026-11-11 21:01:50 -08:00
106 changed files with 7417 additions and 463 deletions
+188
View File
@@ -2326,6 +2326,194 @@ fn a_failed_store_write_keeps_the_stored_module_with_its_grant() {
vectorcraft_plugins::registry::revoke(id);
}
/// Runs the test `name` of this module again in a process of its own → whether this is that
/// process. The list of disabled plug-ins is kept for the whole process, and the other tests here
/// read it again each time they attach a plug-in store.
fn in_own_process(name: &str) -> bool {
const CHILD: &str = "VECTORCRAFT_TEST_PLUGIN_DISABLED_LIST";
if std::env::var_os(CHILD).is_some() {
return true;
}
let output = std::process::Command::new(std::env::current_exe().unwrap())
.args(["--exact", &format!("tests::plugins::{name}"), "--nocapture"])
.env(CHILD, "1")
.output()
.unwrap();
let stdout = String::from_utf8_lossy(&output.stdout);
assert!(
output.status.success() && stdout.contains("test result: ok. 1 passed; 0 failed;"),
"{name} failed in its own process:\n{stdout}\n{}",
String::from_utf8_lossy(&output.stderr),
);
false
}
/// An update whose grant can't be written, and whose old module can't be put back either, stays in
/// the store: a disabled plug-in is turned on, as for any update the store holds. The old grant
/// doesn't cover the new module, so the next start skips it until it is approved.
#[test]
fn an_update_left_in_the_store_by_a_failed_put_back_turns_a_disabled_plugin_on() {
if !in_own_process("an_update_left_in_the_store_by_a_failed_put_back_turns_a_disabled_plugin_on") {
return;
}
use vectorcraft_plugins::{permissions::sha256_hex, registry, safe_mode};
let folder = crate::tests_links::Folder::new("plugin-store-failed-put-back-update");
let id = "test.engine.permissions.putbackupdate";
let first = asking(id, r#"["doc.read"]"#, &json!({"objects": []}));
let changed = asking(id, r#"["doc.read","doc.write"]"#, &json!({"objects": []}));
let mut s = session();
s.attach_plugin_store(folder.0.clone());
run(&mut s, "plugin.install", json!({"dataBase64": vectorcraft_format::base64_encode(&first), "grant": ["doc.read"]}));
run(&mut s, "plugin.setEnabled", json!({"id": id, "enabled": false}));
let grants = folder.0.join("grants.json");
let recorded = std::fs::read(&grants).unwrap();
std::fs::remove_file(&grants).unwrap();
std::fs::create_dir_all(grants.join("block")).unwrap();
crate::store::PUT_BACK_FAILS.set(true);
let update = json!({"dataBase64": vectorcraft_format::base64_encode(&changed), "grant": ["doc.read", "doc.write"], "replace": true});
let installed = s.execute("plugin.install", &update);
crate::store::PUT_BACK_FAILS.set(false);
assert_eq!(installed.unwrap()["hash"], sha256_hex(&changed), "the new module runs in this session");
assert_eq!(registry::get(id).map(|p| p.hash().to_string()), Some(sha256_hex(&changed)));
assert_eq!(std::fs::read(s.stored_plugin_file(id).unwrap()).unwrap(), changed, "the store holds the new module");
assert!(safe_mode::disabled().is_empty(), "the module the store holds is turned on");
let saved = std::fs::read(folder.0.join(crate::store::DISABLED_FILE)).unwrap();
assert!(safe_mode::parse_disabled_file(&saved).is_empty(), "nothing disabled is saved");
std::fs::remove_dir_all(&grants).unwrap();
std::fs::write(&grants, &recorded).unwrap();
registry::remove(id);
registry::revoke(id);
let mut next = session();
next.attach_plugin_store(folder.0.clone());
assert!(registry::get(id).is_none(), "the old grant doesn't cover the new module");
safe_mode::set_disabled(vec![]);
}
/// An update whose grant can't be written, and whose put-back writes the old module and then
/// reports a failure, leaves the old module in the store: a disabled plug-in stays disabled, and the
/// next start doesn't load it.
#[test]
fn an_update_put_back_that_fails_after_writing_keeps_a_disabled_plugin_off() {
if !in_own_process("an_update_put_back_that_fails_after_writing_keeps_a_disabled_plugin_off") {
return;
}
use vectorcraft_plugins::{registry, safe_mode};
let folder = crate::tests_links::Folder::new("plugin-store-put-back-fails-after-write");
let id = "test.engine.permissions.putbacklate";
let first = asking(id, r#"["doc.read"]"#, &json!({"objects": []}));
let changed = asking(id, r#"["doc.read","doc.write"]"#, &json!({"objects": []}));
let mut s = session();
s.attach_plugin_store(folder.0.clone());
run(&mut s, "plugin.install", json!({"dataBase64": vectorcraft_format::base64_encode(&first), "grant": ["doc.read"]}));
run(&mut s, "plugin.setEnabled", json!({"id": id, "enabled": false}));
let disabled = safe_mode::disabled();
assert!(!disabled.is_empty());
let grants = folder.0.join("grants.json");
let recorded = std::fs::read(&grants).unwrap();
std::fs::remove_file(&grants).unwrap();
std::fs::create_dir_all(grants.join("block")).unwrap();
crate::store::PUT_BACK_FAILS_AFTER_WRITE.set(true);
let update = json!({"dataBase64": vectorcraft_format::base64_encode(&changed), "grant": ["doc.read", "doc.write"], "replace": true});
let installed = s.execute("plugin.install", &update);
crate::store::PUT_BACK_FAILS_AFTER_WRITE.set(false);
installed.unwrap();
assert_eq!(std::fs::read(s.stored_plugin_file(id).unwrap()).unwrap(), first, "the store holds the old module");
assert_eq!(safe_mode::disabled(), disabled, "the plug-in stays disabled");
let saved = std::fs::read(folder.0.join(crate::store::DISABLED_FILE)).unwrap();
assert!(!safe_mode::parse_disabled_file(&saved).is_empty(), "the disabled list on disk is kept");
std::fs::remove_dir_all(&grants).unwrap();
std::fs::write(&grants, &recorded).unwrap();
registry::remove(id);
registry::revoke(id);
let mut next = session();
next.attach_plugin_store(folder.0.clone());
assert!(registry::get(id).is_none(), "the disabled old module doesn't load");
safe_mode::set_disabled(vec![]);
}
/// A module without permissions whose install can't remove the old grant, and whose put-back writes
/// the old module and then reports a failure, leaves the old module in the store: a disabled plug-in
/// stays disabled.
#[test]
fn a_plain_update_put_back_that_fails_after_writing_keeps_a_disabled_plugin_off() {
if !in_own_process("a_plain_update_put_back_that_fails_after_writing_keeps_a_disabled_plugin_off") {
return;
}
use vectorcraft_plugins::safe_mode;
let folder = crate::tests_links::Folder::new("plugin-store-plain-put-back-fails-after-write");
let id = "test.engine.permissions.plainputbacklate";
let empty = json!({"objects": []});
let approved = asking(id, r#"["doc.read"]"#, &empty);
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": vectorcraft_format::base64_encode(&approved), "grant": ["doc.read"]}));
run(&mut s, "plugin.setEnabled", json!({"id": id, "enabled": false}));
let disabled = safe_mode::disabled();
assert!(!disabled.is_empty());
let file = s.stored_plugin_file(id).unwrap();
// `1e9` is written back as `1000000000.0`, which makes the rewritten file larger than the 1 MiB that is read.
let entry = serde_json::from_slice::<Vec<Value>>(&std::fs::read(&grants).unwrap()).unwrap().remove(0);
let full = format!(r#"[{entry},{{"n":[{}]}}]"#, vec!["1e9"; 200_000].join(",")).into_bytes();
std::fs::write(&grants, &full).unwrap();
crate::store::PUT_BACK_FAILS_AFTER_WRITE.set(true);
let installed = s.execute("plugin.install", &json!({"dataBase64": vectorcraft_format::base64_encode(&plain), "replace": true}));
crate::store::PUT_BACK_FAILS_AFTER_WRITE.set(false);
installed.unwrap();
assert_eq!(std::fs::read(&file).unwrap(), approved, "the store holds the old module");
assert_eq!(safe_mode::disabled(), disabled, "the plug-in stays disabled");
let saved = std::fs::read(folder.0.join(crate::store::DISABLED_FILE)).unwrap();
assert!(!safe_mode::parse_disabled_file(&saved).is_empty(), "the disabled list on disk is kept");
std::fs::remove_file(&grants).unwrap();
run(&mut s, "plugin.remove", json!({"id": id}));
safe_mode::set_disabled(vec![]);
}
/// A first install whose grant can't be written, and whose module can't be taken out of the store
/// again, stays in the store: the plug-in's file is turned on. No grant covers the module, so the
/// next start skips it until it is approved.
#[test]
fn a_first_install_left_in_the_store_by_a_failed_put_back_turns_its_file_on() {
if !in_own_process("a_first_install_left_in_the_store_by_a_failed_put_back_turns_its_file_on") {
return;
}
use vectorcraft_plugins::{registry, safe_mode};
let folder = crate::tests_links::Folder::new("plugin-store-failed-put-back-first");
let id = "test.engine.permissions.putbackfirst";
let first = asking(id, r#"["doc.read"]"#, &json!({"objects": []}));
let mut s = session();
s.attach_plugin_store(folder.0.clone());
let module = s.stored_plugin_file(id).unwrap();
run(&mut s, "plugin.setEnabled", json!({"file": module, "enabled": false}));
let grants = folder.0.join("grants.json");
std::fs::create_dir_all(grants.join("block")).unwrap();
crate::store::PUT_BACK_FAILS.set(true);
let installed = s.execute("plugin.install", &json!({"dataBase64": vectorcraft_format::base64_encode(&first), "grant": ["doc.read"]}));
crate::store::PUT_BACK_FAILS.set(false);
installed.unwrap();
assert!(registry::get(id).is_some(), "the module runs in this session");
assert_eq!(std::fs::read(&module).unwrap(), first, "the store holds the module");
assert!(safe_mode::disabled().is_empty(), "the file the store holds is turned on");
let saved = std::fs::read(folder.0.join(crate::store::DISABLED_FILE)).unwrap();
assert!(safe_mode::parse_disabled_file(&saved).is_empty(), "nothing disabled is saved");
std::fs::remove_dir_all(&grants).unwrap();
registry::remove(id);
registry::revoke(id);
let mut next = session();
next.attach_plugin_store(folder.0.clone());
assert!(registry::get(id).is_none(), "no grant covers the module");
safe_mode::set_disabled(vec![]);
}
/// A plug-in folder reports a changed module as needing approval and still loads the other files;
/// granted on its own, the changed module loads from the folder. A session without a store uses the
/// grants held by the process.