112 lines
4.9 KiB
JavaScript
112 lines
4.9 KiB
JavaScript
const assert = require("assert");
|
|
const fs = require("fs");
|
|
const os = require("os");
|
|
const path = require("path");
|
|
const {
|
|
replacePluginDirectory,
|
|
resetDirectoryForFullUpdate
|
|
} = require("../src/services/update-manager");
|
|
|
|
const root = fs.mkdtempSync(path.join(os.tmpdir(), "lumi-plugin-update-test-"));
|
|
try {
|
|
const source = path.join(root, "source");
|
|
const target = path.join(root, "target");
|
|
fs.mkdirSync(path.join(source, "data", "config"), { recursive: true });
|
|
fs.mkdirSync(path.join(target, "data", "models"), { recursive: true });
|
|
fs.writeFileSync(path.join(source, "plugin.json"), '{"id":"test"}');
|
|
fs.writeFileSync(path.join(source, "index.js"), "module.exports = 'new';");
|
|
fs.writeFileSync(path.join(source, "data", "config", "default.json"), '{"default":true}');
|
|
fs.writeFileSync(path.join(target, "index.js"), "module.exports = 'old';");
|
|
fs.writeFileSync(path.join(target, "stale.js"), "stale");
|
|
const model = path.join(target, "data", "models", "large.gguf");
|
|
const descriptor = fs.openSync(model, "w");
|
|
fs.ftruncateSync(descriptor, 3 * 1024 * 1024 * 1024);
|
|
fs.closeSync(descriptor);
|
|
|
|
replacePluginDirectory(source, target, { preserveData: true });
|
|
|
|
assert.equal(fs.readFileSync(path.join(target, "index.js"), "utf8"), "module.exports = 'new';");
|
|
assert.equal(fs.existsSync(path.join(target, "stale.js")), false);
|
|
assert.equal(fs.statSync(model).size, 3 * 1024 * 1024 * 1024);
|
|
assert.equal(fs.existsSync(path.join(target, "data", "config", "default.json")), false);
|
|
|
|
const failedSource = path.join(root, "failed-source");
|
|
fs.mkdirSync(failedSource, { recursive: true });
|
|
fs.writeFileSync(path.join(failedSource, "plugin.json"), '{"id":"test"}');
|
|
fs.writeFileSync(path.join(failedSource, "index.js"), "module.exports = 'must-not-land';");
|
|
const originalRename = fs.renameSync;
|
|
fs.renameSync = (from, to) => {
|
|
if (String(from).includes(".target.update-") && to === target) {
|
|
throw Object.assign(new Error("simulated install lock"), { code: "EPERM" });
|
|
}
|
|
return originalRename(from, to);
|
|
};
|
|
try {
|
|
assert.throws(
|
|
() => replacePluginDirectory(failedSource, target, { preserveData: true }),
|
|
/simulated install lock/
|
|
);
|
|
} finally {
|
|
fs.renameSync = originalRename;
|
|
}
|
|
assert.equal(fs.readFileSync(path.join(target, "index.js"), "utf8"), "module.exports = 'new';");
|
|
assert.equal(fs.statSync(model).size, 3 * 1024 * 1024 * 1024);
|
|
assert.equal(fs.readdirSync(root).some((name) => name.includes(".target.update-") || name.includes(".target.backup-")), false);
|
|
|
|
const install = path.join(root, "install");
|
|
for (const relative of [
|
|
"data/feedback/report.json",
|
|
"data/lumi_ai/models/model.gguf",
|
|
"plugins/example/data/settings.json",
|
|
"knowledge/community/local.md",
|
|
"knowledge/corrections/fix.md",
|
|
"knowledge/core/generated.md",
|
|
"knowledge/core/local.md",
|
|
"knowledge/plugins/generated.md",
|
|
"config/local.json",
|
|
"src/stale.js"
|
|
]) {
|
|
const file = path.join(install, relative);
|
|
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
fs.writeFileSync(file, relative);
|
|
}
|
|
fs.writeFileSync(
|
|
path.join(install, "knowledge", "core", "generated.md"),
|
|
"---\ngenerated: true\neditable: false\n---\ngenerated"
|
|
);
|
|
fs.writeFileSync(
|
|
path.join(install, "knowledge", "plugins", "generated.md"),
|
|
"---\ngenerated: true\neditable: false\n---\ngenerated"
|
|
);
|
|
fs.writeFileSync(
|
|
path.join(install, "knowledge", "core", "local.md"),
|
|
"---\ngenerated: false\neditable: true\n---\nlocal"
|
|
);
|
|
resetDirectoryForFullUpdate(install);
|
|
for (const relative of [
|
|
"data/feedback/report.json",
|
|
"data/lumi_ai/models/model.gguf",
|
|
"plugins/example/data/settings.json",
|
|
"knowledge/community/local.md",
|
|
"knowledge/corrections/fix.md",
|
|
"knowledge/core/local.md",
|
|
"config/local.json"
|
|
]) {
|
|
assert.equal(fs.existsSync(path.join(install, relative)), true, `${relative} should be preserved`);
|
|
}
|
|
assert.equal(fs.existsSync(path.join(install, "knowledge/core/generated.md")), false);
|
|
assert.equal(fs.existsSync(path.join(install, "knowledge/plugins/generated.md")), false);
|
|
assert.equal(fs.existsSync(path.join(install, "src/stale.js")), false);
|
|
|
|
const managerSource = fs.readFileSync(path.join(__dirname, "..", "src", "services", "update-manager.js"), "utf8");
|
|
const updateDocs = fs.readFileSync(path.join(__dirname, "..", "docs", "updates.md"), "utf8");
|
|
assert(managerSource.includes("Automatic rollback also failed"));
|
|
assert.equal((managerSource.match(/restoreSnapshot\(record\.id/g) || []).length >= 2, true);
|
|
assert(updateDocs.includes("Preserved Local Data"));
|
|
assert(updateDocs.includes("knowledge/community/"));
|
|
assert(updateDocs.includes("AI models/runtimes"));
|
|
console.log("Update preservation verification passed: core paths, plugin data, transactional replacement, and failed-update rollback wiring.");
|
|
} finally {
|
|
fs.rmSync(root, { recursive: true, force: true });
|
|
}
|