Files
Marcos Paulo cac6b0ed39
ci / ci (macos-latest) (push) Has been cancelled
ci / ci (ubuntu-latest) (push) Has been cancelled
ci / ci (windows-latest) (push) Has been cancelled
feat: registry consumers (grammar, theme, pack), seed reader, wasm plugin host with approval, wanted scan, STIX and Lottie store lookups, plugin auto-disable persistence
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-21 18:35:47 -03:00

232 lines
11 KiB
Rust

//! Install, update, remove and rollback against a fake transport and a temp store.
mod addons_support;
use addons_support::{TINY, entry, index, manifest, serve, wasm};
use notescraft_engine::addons::{
Ctx, Ed25519, FakeTransport, FakeVerifier, Index, Kind, Seed, Source, Store, Version, fetch_files, install, key, remove, rollback, update, write_tree,
};
const GRAMMAR: &str = "twig.sublime-syntax";
struct Env {
dir: std::path::PathBuf,
store: Store,
}
impl Env {
fn new(name: &str) -> Self {
let dir = std::env::temp_dir().join(format!("notescraft-addons-install-{}-{name}", std::process::id()));
let _ = std::fs::remove_dir_all(&dir);
let store = Store::open(&dir).unwrap();
Self { dir, store }
}
fn ctx<'a>(&'a self, transport: &'a FakeTransport, verifier: &'a FakeVerifier) -> Ctx<'a> {
Ctx { store: &self.store, transport, verifier, app_version: v(0, 5, 0), seed: None }
}
}
impl Drop for Env {
fn drop(&mut self) {
let _ = std::fs::remove_dir_all(&self.dir);
}
}
fn v(major: u16, minor: u16, patch: u16) -> Version {
Version { major, minor, patch }
}
const OK: FakeVerifier = FakeVerifier { accept: true };
fn grammar_index(version: &str, bytes: &[u8]) -> Index {
index(&[entry("grammar", "twig", version, &[(GRAMMAR, bytes)], &[])], &[])
}
fn grammar_net(version: &str, bytes: &[u8]) -> FakeTransport {
serve(FakeTransport::new(), "grammar", "twig", version, &[(GRAMMAR, bytes)])
}
fn error_of<T: std::fmt::Debug>(result: notescraft_engine::Result<T>) -> String {
result.expect_err("must fail").to_string()
}
#[test]
fn a_clean_install_commits_state_and_files() {
let env = Env::new("clean");
let report = install(&env.ctx(&grammar_net("1.0.0", TINY.as_bytes()), &OK), &grammar_index("1.0.0", TINY.as_bytes()), Kind::Grammar, "twig", None).unwrap();
assert_eq!((report.version, report.previous, report.source), (v(1, 0, 0), None, Source::Registry));
let state = env.store.load().unwrap();
assert_eq!(state.installed[&key(Kind::Grammar, "twig")].version, v(1, 0, 0));
let file = env.store.version_dir(Kind::Grammar, "twig", v(1, 0, 0)).unwrap().join(GRAMMAR);
assert_eq!(std::fs::read(file).unwrap(), TINY.as_bytes());
}
#[test]
fn a_bad_download_changes_nothing() {
let env = Env::new("bad");
let idx = grammar_index("1.0.0", TINY.as_bytes());
let short = grammar_net("1.0.0", &TINY.as_bytes()[..10]);
assert!(install(&env.ctx(&short, &OK), &idx, Kind::Grammar, "twig", None).is_err());
let wrong = grammar_net("1.0.0", &TINY.to_uppercase().into_bytes());
assert!(error_of(install(&env.ctx(&wrong, &OK), &idx, Kind::Grammar, "twig", None)).contains("sha256"));
let long = grammar_net("1.0.0", &[TINY.as_bytes(), b"x"].concat());
assert!(install(&env.ctx(&long, &OK), &idx, Kind::Grammar, "twig", None).is_err());
assert!(error_of(install(&env.ctx(&FakeTransport::new(), &OK), &idx, Kind::Grammar, "twig", None)).contains("offline"));
assert!(env.store.load().unwrap().installed.is_empty());
assert_eq!(env.store.gc(&env.store.load().unwrap()).unwrap(), 0, "no folder was written");
}
#[test]
fn a_content_check_failure_writes_nothing() {
let env = Env::new("content");
let junk = b"nope: [".as_slice();
let idx = grammar_index("1.0.0", junk);
assert!(install(&env.ctx(&grammar_net("1.0.0", junk), &OK), &idx, Kind::Grammar, "twig", None).is_err());
assert!(!env.store.version_dir(Kind::Grammar, "twig", v(1, 0, 0)).unwrap().exists());
}
#[test]
fn the_gates_refuse_before_any_download() {
let env = Env::new("gates");
let net = grammar_net("1.0.0", TINY.as_bytes());
let idx = grammar_index("1.0.0", TINY.as_bytes());
assert!(error_of(install(&env.ctx(&net, &FakeVerifier { accept: false }), &idx, Kind::Grammar, "twig", None)).contains("refuses"));
let real = Ed25519::new(vec![]);
let ctx = Ctx { store: &env.store, transport: &net, verifier: &real, app_version: v(0, 5, 0), seed: None };
assert!(error_of(install(&ctx, &idx, Kind::Grammar, "twig", None)).contains("unknown signing key"));
let old_app = Ctx { app_version: v(0, 0, 9), ..env.ctx(&net, &OK) };
assert!(error_of(install(&old_app, &idx, Kind::Grammar, "twig", None)).contains("incompatible"));
let revoked = index(&[entry("grammar", "twig", "1.0.0", &[(GRAMMAR, TINY.as_bytes())], &[])], &[("twig", "1.0.0")]);
assert!(error_of(install(&env.ctx(&net, &OK), &revoked, Kind::Grammar, "twig", None)).contains("revoked"));
assert!(error_of(install(&env.ctx(&net, &OK), &idx, Kind::Grammar, "missing", None)).contains("not_found"));
assert!(error_of(install(&env.ctx(&net, &OK), &idx, Kind::Grammar, "twig", Some(v(2, 0, 0)))).contains("not_found"));
let snippet = index(&[entry("snippet", "s", "1.0.0", &[("s.md", b"hi")], &[])], &[]);
assert!(error_of(install(&env.ctx(&net, &OK), &snippet, Kind::Snippet, "s", None)).contains("no use"));
assert!(net.asked().is_empty(), "nothing was fetched: {:?}", net.asked());
assert!(env.store.load().unwrap().installed.is_empty());
}
#[test]
fn a_crash_before_the_state_flip_leaves_the_old_state_and_gc_cleans_up() {
let env = Env::new("crash");
let (idx, net) = (grammar_index("1.0.0", TINY.as_bytes()), grammar_net("1.0.0", TINY.as_bytes()));
let ctx = env.ctx(&net, &OK);
let entry = idx.find(Kind::Grammar, "twig").unwrap();
let staged = fetch_files(&ctx, entry).unwrap();
write_tree(&env.store, entry, &staged).unwrap();
let dir = env.store.version_dir(Kind::Grammar, "twig", v(1, 0, 0)).unwrap();
assert!(dir.is_dir());
assert!(env.store.load().unwrap().installed.is_empty(), "state was never flipped");
assert_eq!(env.store.gc(&env.store.load().unwrap()).unwrap(), 1);
assert!(!dir.exists());
write_tree(&env.store, entry, &staged).unwrap();
write_tree(&env.store, entry, &staged).unwrap();
}
#[test]
fn update_keeps_previous_and_rollback_swaps() {
let env = Env::new("update");
install(&env.ctx(&grammar_net("1.0.0", TINY.as_bytes()), &OK), &grammar_index("1.0.0", TINY.as_bytes()), Kind::Grammar, "twig", None).unwrap();
let newer = format!("{TINY}# 1.1\n");
let (idx, net) = (grammar_index("1.1.0", newer.as_bytes()), grammar_net("1.1.0", newer.as_bytes()));
let report = update(&env.ctx(&net, &OK), &idx, Kind::Grammar, "twig").unwrap();
assert_eq!((report.version, report.previous), (v(1, 1, 0), Some(v(1, 0, 0))));
assert!(env.store.version_dir(Kind::Grammar, "twig", v(1, 0, 0)).unwrap().is_dir(), "the old version stays for rollback");
assert!(error_of(update(&env.ctx(&net, &OK), &idx, Kind::Grammar, "twig")).contains("already installed"));
let back = rollback(&env.store, Kind::Grammar, "twig").unwrap();
assert_eq!((back.version, back.previous), (v(1, 0, 0), Some(v(1, 1, 0))));
let forward = rollback(&env.store, Kind::Grammar, "twig").unwrap();
assert_eq!(forward.version, v(1, 1, 0));
let old = (grammar_index("1.0.0", TINY.as_bytes()), grammar_net("1.0.0", TINY.as_bytes()));
assert!(error_of(install(&env.ctx(&old.1, &OK), &old.0, Kind::Grammar, "twig", None)).contains("rollback"), "a downgrade is never an install");
}
#[test]
fn update_and_rollback_need_an_installed_add_on() {
let env = Env::new("needs");
let (idx, net) = (grammar_index("1.0.0", TINY.as_bytes()), grammar_net("1.0.0", TINY.as_bytes()));
assert!(error_of(update(&env.ctx(&net, &OK), &idx, Kind::Grammar, "twig")).contains("not installed"));
assert!(error_of(rollback(&env.store, Kind::Grammar, "twig")).contains("not installed"));
install(&env.ctx(&net, &OK), &idx, Kind::Grammar, "twig", None).unwrap();
assert!(error_of(rollback(&env.store, Kind::Grammar, "twig")).contains("earlier"));
}
#[test]
fn remove_commits_first_then_deletes_the_files() {
let env = Env::new("remove");
install(&env.ctx(&grammar_net("1.0.0", TINY.as_bytes()), &OK), &grammar_index("1.0.0", TINY.as_bytes()), Kind::Grammar, "twig", None).unwrap();
remove(&env.store, Kind::Grammar, "twig").unwrap();
assert!(env.store.load().unwrap().installed.is_empty());
assert!(!env.store.version_dir(Kind::Grammar, "twig", v(1, 0, 0)).unwrap().exists());
assert!(error_of(remove(&env.store, Kind::Grammar, "twig")).contains("not_found"));
}
#[derive(Debug)]
struct Fixed(Vec<u8>);
impl Seed for Fixed {
fn read(&self, _: Kind, _: &str, _: Version, _: &str) -> Option<Vec<u8>> {
Some(self.0.clone())
}
}
#[test]
fn a_seed_installs_offline_and_a_tampered_seed_is_ignored() {
let env = Env::new("seed");
let idx = grammar_index("1.0.0", TINY.as_bytes());
let seed = Fixed(TINY.as_bytes().to_vec());
let offline = FakeTransport::new();
let ctx = Ctx { seed: Some(&seed), ..env.ctx(&offline, &OK) };
let report = install(&ctx, &idx, Kind::Grammar, "twig", None).unwrap();
assert_eq!(report.source, Source::Seed);
assert_eq!(offline.asked().len(), 0);
let env = Env::new("seed-bad");
let tampered = Fixed(b"tampered".to_vec());
let ctx = Ctx { seed: Some(&tampered), ..env.ctx(&offline, &OK) };
assert!(error_of(install(&ctx, &idx, Kind::Grammar, "twig", None)).contains("offline"), "falls back to the network, which is down");
}
#[test]
fn a_plugin_installs_with_its_manifest_checked() {
let env = Env::new("plugin");
let text = manifest("dice", "1.0.0", &["vault.read"]);
let files: &[(&str, &[u8])] = &[("manifest.json", text.as_bytes()), ("plugin.wasm", wasm())];
let idx = index(&[entry("plugin", "dice", "1.0.0", files, &["vault.read"])], &[]);
let net = serve(FakeTransport::new(), "plugin", "dice", "1.0.0", files);
install(&env.ctx(&net, &OK), &idx, Kind::Plugin, "dice", None).unwrap();
let dir = env.store.version_dir(Kind::Plugin, "dice", v(1, 0, 0)).unwrap();
assert!(dir.join("manifest.json").is_file() && dir.join("plugin.wasm").is_file());
}
#[test]
fn the_installed_table_cap_is_enforced() {
let env = Env::new("cap");
let mut state = env.store.load().unwrap();
for n in 0..512 {
state.installed.insert(
format!("grammar:g{n}"),
notescraft_engine::addons::Installed {
version: v(1, 0, 0),
previous: None,
source: Source::Registry,
installed_at: "t".into(),
sha256_of_files: "e".repeat(64),
},
);
}
env.store.commit(&state).unwrap();
let (idx, net) = (grammar_index("1.0.0", TINY.as_bytes()), grammar_net("1.0.0", TINY.as_bytes()));
assert!(error_of(install(&env.ctx(&net, &OK), &idx, Kind::Grammar, "twig", None)).contains("limit"));
}
#[test]
fn a_font_entry_installs() {
let env = Env::new("font");
let font = index(&[entry("font", "stix-math", "1.0.0", &[("stix.ttf", &[0, 1, 0, 0])], &[])], &[]);
let net = serve(FakeTransport::new(), "font", "stix-math", "1.0.0", &[("stix.ttf", &[0, 1, 0, 0])]);
let report = install(&env.ctx(&net, &OK), &font, Kind::Font, "stix-math", None).unwrap();
assert_eq!(report.version, v(1, 0, 0));
assert!(env.store.load().unwrap().installed.contains_key(&key(Kind::Font, "stix-math")));
}