// Unit tests for the desktop plugin-tree maintenance (plain node, no Electron). // Run: node --test cmd/gui/plugin-sync.test.js const test = require("node:test"); const assert = require("node:assert"); const fs = require("fs"); const os = require("os"); const path = require("path"); const { BUILTIN_PLUGINS, ensurePluginDirConfigured, syncBundledPluginsOnUpgrade } = require("./plugin-sync"); const tmp = () => fs.mkdtempSync(path.join(os.tmpdir(), "plugin-sync-")); test("back-fills plugin_dir into an existing profile without it", () => { const dir = tmp(); const cfg = path.join(dir, "config.yaml"); fs.writeFileSync(cfg, 'listen: 127.0.0.1:8787\ndefault_model: AUTO\nkeys:\n - key: k\n role: admin\n'); const plugins = path.join(dir, "plugins"); assert.equal(ensurePluginDirConfigured(cfg, plugins, null), "added"); const out = fs.readFileSync(cfg, "utf-8"); assert.match(out, /^plugin_dir: "/m); assert.ok(out.includes(plugins.replace(/\\/g, "\\\\")) || out.includes(plugins), "path written"); // the rest of the profile must survive untouched assert.match(out, /^listen: 127\.0\.0\.1:8787$/m); assert.match(out, /^ - key: k$/m); }); test("leaves a profile that already sets plugin_dir alone", () => { const dir = tmp(); const cfg = path.join(dir, "config.yaml"); const orig = 'plugin_dir: "/custom/plugins"\nlisten: ":1"\n'; fs.writeFileSync(cfg, orig); assert.equal(ensurePluginDirConfigured(cfg, "/whatever", null), "present"); assert.equal(fs.readFileSync(cfg, "utf-8"), orig); }); test("missing config is not created by the back-fill", () => { const dir = tmp(); const cfg = path.join(dir, "config.yaml"); assert.equal(ensurePluginDirConfigured(cfg, path.join(dir, "p"), null), "absent"); assert.equal(fs.existsSync(cfg), false); }); test("upgrade drops builtin plugins (keeping a backup) but not user plugins", () => { const dir = tmp(); const plugins = path.join(dir, "plugins"); fs.mkdirSync(plugins, { recursive: true }); for (const f of BUILTIN_PLUGINS) fs.writeFileSync(path.join(plugins, f), "OLD"); fs.writeFileSync(path.join(plugins, "my-own.lua"), "MINE"); fs.writeFileSync(path.join(dir, ".plugin-seed-version"), "1.0.0\n"); const r = syncBundledPluginsOnUpgrade(dir, plugins, "2.0.42", null); assert.equal(r.changed, true); assert.deepEqual(r.dropped.sort(), [...BUILTIN_PLUGINS].sort()); for (const f of BUILTIN_PLUGINS) { assert.equal(fs.existsSync(path.join(plugins, f)), false, f + " dropped"); assert.equal(fs.readFileSync(path.join(plugins, f + ".prev-1.0.0"), "utf-8"), "OLD"); } assert.equal(fs.readFileSync(path.join(plugins, "my-own.lua"), "utf-8"), "MINE"); assert.equal(fs.readFileSync(path.join(dir, ".plugin-seed-version"), "utf-8").trim(), "2.0.42"); }); test("same version is a no-op (nothing dropped)", () => { const dir = tmp(); const plugins = path.join(dir, "plugins"); fs.mkdirSync(plugins, { recursive: true }); fs.writeFileSync(path.join(plugins, "channels.lua"), "CURRENT"); fs.writeFileSync(path.join(dir, ".plugin-seed-version"), "2.0.42\n"); const r = syncBundledPluginsOnUpgrade(dir, plugins, "2.0.42", null); assert.equal(r.changed, false); assert.equal(fs.readFileSync(path.join(plugins, "channels.lua"), "utf-8"), "CURRENT"); }); test("first run on a fresh profile just stamps the version", () => { const dir = tmp(); const plugins = path.join(dir, "plugins"); fs.mkdirSync(plugins, { recursive: true }); const r = syncBundledPluginsOnUpgrade(dir, plugins, "2.0.42", null); assert.equal(r.changed, true); assert.deepEqual(r.dropped, []); assert.equal(fs.existsSync(path.join(dir, ".plugin-seed-version")), true); });