Files
Marcos Paulo e26672c98c feat(engine-plugins): wave 9
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-14 00:32:04 -03:00

99 lines
4.1 KiB
Rust

//! Small real WASM packages for the engine plugin integration tests.
//!
//! Every fixture is a full ABI v1 module built from WAT at test time, so the
//! tests exercise the same loader a user plugin goes through.
#![allow(dead_code)] // shared test helpers: each test file uses only some of them
use std::path::Path;
use notescraft_engine::{AppSettings, Registry, Session, register_core};
use notescraft_plugins::Permission;
use serde_json::json;
/// An empty JSON result packed the way ABI v1 does: `ptr << 32 | len`.
pub const EMPTY: &str = "(i64.const 68719476738)";
/// Makes an ABI v1 module with caller-supplied imports and command body.
///
/// The module carries the data blocks the fixtures point at: `{}` at 16 (the
/// empty result), `source.md` at 128, `copied.md` at 160 and
/// `# Copy\0a[[source]] #bridge` at 192.
#[must_use]
pub fn module(imports: &str, body: &str) -> Vec<u8> {
build(imports, body, "")
}
/// [`module`], plus a data block at 512 holding `path`: a fixture that writes to
/// a path of its own (a reserved one, a traversal) needs the exact text.
#[must_use]
pub fn module_with_path(imports: &str, body: &str, path: &str) -> Vec<u8> {
let escaped = path.replace('\\', "\\\\").replace('"', "\\\"");
build(imports, body, &format!("(data (i32.const 512) \"{escaped}\")"))
}
/// Compiles the module text around the caller's imports, body and extra data.
fn build(imports: &str, body: &str, extra_data: &str) -> Vec<u8> {
let text = format!(
r##"(module
{imports}
(memory (export "memory") 32)
(global $bump (mut i32) (i32.const 65536))
(global $count (mut i32) (i32.const 0))
(data (i32.const 16) "{{}}")
(data (i32.const 128) "source.md")
(data (i32.const 160) "copied.md")
(data (i32.const 192) "# Copy\0a[[source]] #bridge")
{extra_data}
(func (export "ns_alloc") (param $len i32) (result i32)
(local $ptr i32)
(local.set $ptr (global.get $bump))
(global.set $bump (i32.add (local.get $ptr) (local.get $len)))
(local.get $ptr))
(func (export "ns_free") (param i32 i32))
(func (export "ns_abi_version") (result i32) (i32.const 1))
(func (export "ns_on_load") (result i32) (i32.const 0))
(func (export "ns_on_unload") (result i32) (i32.const 0))
(func (export "ns_on_event") (param i32 i32) (result i32) (i32.const 0))
(func (export "ns_run_command") (param $cmd i32) (param $cmdlen i32)
(param $args i32) (param $argslen i32) (result i64)
{body}))"##
);
wat::parse_str(text).expect("compile WAT")
}
/// Installs an actual plugin package under `.notescraft/plugins/<id>/`.
pub fn install(root: &Path, id: &str, permissions: &[Permission], wasm: &[u8]) {
let dir = root.join(".notescraft/plugins").join(id);
std::fs::create_dir_all(&dir).expect("plugin folder");
let manifest = json!({
"id": id, "name": id, "version": "1.2.3", "min_app_version": "1",
"permissions": permissions,
"commands": [{"id": "run", "name": "Run"}, {"id": "other", "name": "Other"}]
});
std::fs::write(dir.join("manifest.json"), manifest.to_string()).expect("manifest");
std::fs::write(dir.join("plugin.wasm"), wasm).expect("wasm");
}
/// The settings document that enables and approves `id`.
pub fn settings_for(id: &str, permissions: &[Permission]) -> AppSettings {
let mut settings = AppSettings::default();
settings.plugins.enabled.insert(id.to_owned());
settings.plugins.approved.insert(id.to_owned(), permissions.to_vec());
settings
}
/// Applies enabled + approved for `id` and loads the plugins of the vault.
pub fn approved(session: &mut Session, id: &str, permissions: &[Permission]) {
session.apply_app_settings(&settings_for(id, permissions)).expect("apply plugin settings");
session.load_plugins().expect("load plugins");
}
/// The core registry every transport uses.
#[must_use]
pub fn registry() -> Registry<Session> {
let mut registry = Registry::new();
register_core(&mut registry).expect("core commands");
registry
}