[Init] Initial commit - NetMesh terminal manager
Some checks failed
build-packages / resolve bundled mosh-client (push) Has been cancelled
build-packages / resolve bundled et-client (push) Has been cancelled
build-packages / build-macos (push) Has been cancelled
build-packages / build-windows (push) Has been cancelled
build-packages / build-linux-x64 (push) Has been cancelled
build-packages / build-linux-arm64 (push) Has been cancelled
build-packages / release (push) Has been cancelled
build-packages / update Nix release metadata (push) Has been cancelled
build-packages / bump homebrew tap (push) Has been cancelled
test / lint-and-test (push) Has been cancelled
AI automation / Route event (push) Has been cancelled
AI automation / Hand reopened issue to maintainers (push) Has been cancelled
AI automation / Clean source issue state (push) Has been cancelled
AI automation / Reconcile handoffs (push) Has been cancelled
AI automation / Classify issue (push) Has been cancelled
AI automation / Claude Code smoke (push) Has been cancelled
AI automation / Review issue follow-up (push) Has been cancelled
AI automation / Publish issue follow-up (push) Has been cancelled
AI automation / Implement with Claude Code (push) Has been cancelled
AI automation / Publish implement PR (push) Has been cancelled
AI automation / Continue queued issue comments (push) Has been cancelled
AI automation / Codex review loop (push) Has been cancelled
AI automation / Publish Codex fix (push) Has been cancelled
AI automation / Clear Codex dispatch marker (push) Has been cancelled
AI automation / Own PR re-request Codex (push) Has been cancelled
AI automation / External PR re-request Codex (push) Has been cancelled
AI automation / Poll Codex reaction / retry (push) Has been cancelled
build-et-binaries / build-linux-x64 (push) Has been cancelled
build-et-binaries / build-linux-arm64 (push) Has been cancelled
build-et-binaries / build-macos-universal (push) Has been cancelled
build-et-binaries / build-windows-x64 (push) Has been cancelled
build-et-binaries / release (push) Has been cancelled
Some checks failed
build-packages / resolve bundled mosh-client (push) Has been cancelled
build-packages / resolve bundled et-client (push) Has been cancelled
build-packages / build-macos (push) Has been cancelled
build-packages / build-windows (push) Has been cancelled
build-packages / build-linux-x64 (push) Has been cancelled
build-packages / build-linux-arm64 (push) Has been cancelled
build-packages / release (push) Has been cancelled
build-packages / update Nix release metadata (push) Has been cancelled
build-packages / bump homebrew tap (push) Has been cancelled
test / lint-and-test (push) Has been cancelled
AI automation / Route event (push) Has been cancelled
AI automation / Hand reopened issue to maintainers (push) Has been cancelled
AI automation / Clean source issue state (push) Has been cancelled
AI automation / Reconcile handoffs (push) Has been cancelled
AI automation / Classify issue (push) Has been cancelled
AI automation / Claude Code smoke (push) Has been cancelled
AI automation / Review issue follow-up (push) Has been cancelled
AI automation / Publish issue follow-up (push) Has been cancelled
AI automation / Implement with Claude Code (push) Has been cancelled
AI automation / Publish implement PR (push) Has been cancelled
AI automation / Continue queued issue comments (push) Has been cancelled
AI automation / Codex review loop (push) Has been cancelled
AI automation / Publish Codex fix (push) Has been cancelled
AI automation / Clear Codex dispatch marker (push) Has been cancelled
AI automation / Own PR re-request Codex (push) Has been cancelled
AI automation / External PR re-request Codex (push) Has been cancelled
AI automation / Poll Codex reaction / retry (push) Has been cancelled
build-et-binaries / build-linux-x64 (push) Has been cancelled
build-et-binaries / build-linux-arm64 (push) Has been cancelled
build-et-binaries / build-macos-universal (push) Has been cancelled
build-et-binaries / build-windows-x64 (push) Has been cancelled
build-et-binaries / release (push) Has been cancelled
This commit is contained in:
490
electron/plugins/pluginManager.test.cjs
Normal file
490
electron/plugins/pluginManager.test.cjs
Normal file
@@ -0,0 +1,490 @@
|
||||
"use strict";
|
||||
|
||||
const assert = require("node:assert/strict");
|
||||
const test = require("node:test");
|
||||
|
||||
const { PluginManager } = require("./pluginManager.cjs");
|
||||
|
||||
test("failed activation leaves a newly installed plugin disabled", async () => {
|
||||
const plugin = { id: "com.example.activation-failure", enabled: true };
|
||||
let enabled = true;
|
||||
const manager = new PluginManager({
|
||||
database: {
|
||||
close() {},
|
||||
listPlugins: () => [],
|
||||
setEnabled(pluginId, nextEnabled) {
|
||||
assert.equal(pluginId, plugin.id);
|
||||
enabled = nextEnabled;
|
||||
},
|
||||
},
|
||||
packageStore: {
|
||||
async initialize() {},
|
||||
async install() { return plugin; },
|
||||
},
|
||||
runtimeSupervisor: {
|
||||
async start() { throw new Error("activation failed"); },
|
||||
async startEnabled() {},
|
||||
async stop() {},
|
||||
},
|
||||
});
|
||||
|
||||
await assert.rejects(manager.install("/tmp/plugin.ncpkg", { enable: true }), /activation failed/);
|
||||
assert.equal(enabled, false);
|
||||
});
|
||||
|
||||
test("management mutations are serialized in invocation order", async () => {
|
||||
const calls = [];
|
||||
let releaseInstall;
|
||||
const installBlocked = new Promise((resolve) => { releaseInstall = resolve; });
|
||||
const manager = new PluginManager({
|
||||
database: {
|
||||
close() {},
|
||||
getActivePlugin: (pluginId) => ({ id: pluginId, enabled: false }),
|
||||
listPlugins: () => [],
|
||||
setEnabled(pluginId, enabled) { calls.push(`enabled:${pluginId}:${enabled}`); },
|
||||
},
|
||||
packageStore: {
|
||||
async initialize() {},
|
||||
async install() {
|
||||
calls.push("install:start");
|
||||
await installBlocked;
|
||||
calls.push("install:end");
|
||||
return { id: "com.example.serial", enabled: false };
|
||||
},
|
||||
},
|
||||
runtimeSupervisor: {
|
||||
async startEnabled() {},
|
||||
async stop(pluginId) { calls.push(`stop:${pluginId}`); },
|
||||
},
|
||||
});
|
||||
|
||||
const install = manager.install("/tmp/plugin.ncpkg");
|
||||
const disable = manager.setEnabled("com.example.serial", false);
|
||||
await new Promise((resolve) => setImmediate(resolve));
|
||||
assert.deepEqual(calls, ["install:start"]);
|
||||
releaseInstall();
|
||||
await Promise.all([install, disable]);
|
||||
assert.deepEqual(calls, [
|
||||
"install:start",
|
||||
"install:end",
|
||||
"enabled:com.example.serial:false",
|
||||
"stop:com.example.serial",
|
||||
]);
|
||||
});
|
||||
|
||||
test("explicit enable clears the active version quarantine before starting", async () => {
|
||||
const calls = [];
|
||||
const pluginId = "com.example.recover";
|
||||
const manager = new PluginManager({
|
||||
database: {
|
||||
close() {},
|
||||
getActivePlugin: () => ({
|
||||
id: pluginId,
|
||||
activeVersion: "1.0.0",
|
||||
enabled: false,
|
||||
runtime: { quarantinedAt: 123 },
|
||||
}),
|
||||
listPlugins: () => [],
|
||||
clearQuarantine(id, version) { calls.push(`recover:${id}@${version}`); },
|
||||
setEnabled(id, enabled) { calls.push(`enabled:${id}:${enabled}`); },
|
||||
},
|
||||
packageStore: { async initialize() {} },
|
||||
runtimeSupervisor: {
|
||||
async startEnabled() {},
|
||||
async start(id) { calls.push(`start:${id}`); },
|
||||
},
|
||||
});
|
||||
|
||||
await manager.setEnabled(pluginId, true);
|
||||
assert.deepEqual(calls, [
|
||||
`recover:${pluginId}@1.0.0`,
|
||||
`enabled:${pluginId}:true`,
|
||||
`start:${pluginId}`,
|
||||
]);
|
||||
});
|
||||
|
||||
test("installing an enabled version replaces the active runtime", async () => {
|
||||
const calls = [];
|
||||
const plugin = { id: "com.example.upgrade", enabled: true };
|
||||
const manager = new PluginManager({
|
||||
database: {
|
||||
close() {},
|
||||
listPlugins: () => [],
|
||||
setEnabled(pluginId, enabled) { calls.push(`enabled:${pluginId}:${enabled}`); },
|
||||
},
|
||||
packageStore: {
|
||||
async initialize() {},
|
||||
async install(_archivePath, options) {
|
||||
await options.beforeActivate({
|
||||
pluginId: plugin.id,
|
||||
version: "2.0.0",
|
||||
previousPlugin: {
|
||||
id: plugin.id,
|
||||
enabled: true,
|
||||
activeVersion: "1.0.0",
|
||||
},
|
||||
reason: "switch-active-version",
|
||||
});
|
||||
calls.push("activate:2.0.0");
|
||||
return plugin;
|
||||
},
|
||||
},
|
||||
runtimeSupervisor: {
|
||||
async startEnabled() {},
|
||||
async stop(pluginId) { calls.push(`stop:${pluginId}`); },
|
||||
async start(pluginId) { calls.push(`start:${pluginId}`); },
|
||||
},
|
||||
});
|
||||
assert.equal(await manager.install("/tmp/upgrade.ncpkg"), plugin);
|
||||
assert.deepEqual(calls, [
|
||||
"enabled:com.example.upgrade:false",
|
||||
"stop:com.example.upgrade",
|
||||
"activate:2.0.0",
|
||||
"start:com.example.upgrade",
|
||||
]);
|
||||
});
|
||||
|
||||
test("failed version preparation restores the previously enabled runtime", async () => {
|
||||
const calls = [];
|
||||
const pluginId = "com.example.upgrade-rollback";
|
||||
let activePlugin = {
|
||||
id: pluginId,
|
||||
enabled: true,
|
||||
activeVersion: "1.0.0",
|
||||
};
|
||||
const manager = new PluginManager({
|
||||
database: {
|
||||
close() {},
|
||||
getActivePlugin: () => ({ ...activePlugin }),
|
||||
listPlugins: () => [],
|
||||
setEnabled(id, enabled) {
|
||||
assert.equal(id, pluginId);
|
||||
activePlugin = { ...activePlugin, enabled };
|
||||
calls.push(`enabled:${enabled}`);
|
||||
},
|
||||
},
|
||||
packageStore: {
|
||||
async initialize() {},
|
||||
async install(_archivePath, options) {
|
||||
await options.beforeActivate({
|
||||
pluginId,
|
||||
version: "2.0.0",
|
||||
previousPlugin: { ...activePlugin },
|
||||
reason: "switch-active-version",
|
||||
});
|
||||
calls.push("prepare:failed");
|
||||
throw new Error("database switch failed");
|
||||
},
|
||||
},
|
||||
runtimeSupervisor: {
|
||||
async startEnabled() {},
|
||||
async stop(id) { calls.push(`stop:${id}`); },
|
||||
async start(id) { calls.push(`start:${id}`); },
|
||||
},
|
||||
});
|
||||
|
||||
await assert.rejects(manager.install("/tmp/upgrade.ncpkg"), /database switch failed/);
|
||||
assert.deepEqual(calls, [
|
||||
"enabled:false",
|
||||
`stop:${pluginId}`,
|
||||
"prepare:failed",
|
||||
"enabled:true",
|
||||
`start:${pluginId}`,
|
||||
]);
|
||||
assert.equal(activePlugin.enabled, true);
|
||||
});
|
||||
|
||||
test("failed upgraded-version activation restores and restarts the previous version", async () => {
|
||||
const calls = [];
|
||||
const pluginId = "com.example.upgrade-activation-rollback";
|
||||
let activePlugin = {
|
||||
id: pluginId,
|
||||
enabled: true,
|
||||
activeVersion: "1.0.0",
|
||||
};
|
||||
const manager = new PluginManager({
|
||||
database: {
|
||||
close() {},
|
||||
getActivePlugin: () => ({ ...activePlugin }),
|
||||
listPlugins: () => [],
|
||||
setActiveVersion(id, version, options) {
|
||||
assert.equal(id, pluginId);
|
||||
assert.deepEqual(options, { enabled: true, expectedActiveVersion: "2.0.0" });
|
||||
activePlugin = { ...activePlugin, activeVersion: version, enabled: options.enabled };
|
||||
calls.push(`active:${version}:${options.enabled}`);
|
||||
},
|
||||
setEnabled(id, enabled) {
|
||||
assert.equal(id, pluginId);
|
||||
activePlugin = { ...activePlugin, enabled };
|
||||
calls.push(`enabled:${enabled}`);
|
||||
},
|
||||
},
|
||||
packageStore: {
|
||||
async initialize() {},
|
||||
async install(_archivePath, options) {
|
||||
await options.beforeActivate({
|
||||
pluginId,
|
||||
version: "2.0.0",
|
||||
previousPlugin: { ...activePlugin },
|
||||
reason: "switch-active-version",
|
||||
});
|
||||
activePlugin = { ...activePlugin, activeVersion: "2.0.0", enabled: true };
|
||||
calls.push("active:2.0.0:true");
|
||||
return { ...activePlugin };
|
||||
},
|
||||
},
|
||||
runtimeSupervisor: {
|
||||
async startEnabled() {},
|
||||
async stop(id) { calls.push(`stop:${id}`); },
|
||||
async start(id) {
|
||||
calls.push(`start:${id}@${activePlugin.activeVersion}`);
|
||||
if (activePlugin.activeVersion === "2.0.0") throw new Error("new activation failed");
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
await assert.rejects(manager.install("/tmp/upgrade.ncpkg"), /new activation failed/);
|
||||
assert.deepEqual(calls, [
|
||||
"enabled:false",
|
||||
`stop:${pluginId}`,
|
||||
"active:2.0.0:true",
|
||||
`start:${pluginId}@2.0.0`,
|
||||
"enabled:false",
|
||||
"active:1.0.0:true",
|
||||
`start:${pluginId}@1.0.0`,
|
||||
]);
|
||||
assert.deepEqual(activePlugin, { id: pluginId, enabled: true, activeVersion: "1.0.0" });
|
||||
});
|
||||
|
||||
test("failed previous-version restart leaves the restored version disabled", async () => {
|
||||
const pluginId = "com.example.upgrade-rollback-fails";
|
||||
let activePlugin = { id: pluginId, enabled: true, activeVersion: "1.0.0" };
|
||||
const manager = new PluginManager({
|
||||
database: {
|
||||
close() {},
|
||||
getActivePlugin: () => ({ ...activePlugin }),
|
||||
listPlugins: () => [],
|
||||
setActiveVersion(_id, version, options) {
|
||||
activePlugin = { ...activePlugin, activeVersion: version, enabled: options.enabled };
|
||||
},
|
||||
setEnabled(_id, enabled) { activePlugin = { ...activePlugin, enabled }; },
|
||||
},
|
||||
packageStore: {
|
||||
async initialize() {},
|
||||
async install(_archivePath, options) {
|
||||
await options.beforeActivate({
|
||||
pluginId,
|
||||
version: "2.0.0",
|
||||
previousPlugin: { ...activePlugin },
|
||||
reason: "switch-active-version",
|
||||
});
|
||||
activePlugin = { ...activePlugin, activeVersion: "2.0.0", enabled: true };
|
||||
return { ...activePlugin };
|
||||
},
|
||||
},
|
||||
runtimeSupervisor: {
|
||||
async startEnabled() {},
|
||||
async stop() {},
|
||||
async start() { throw new Error("runtime unavailable"); },
|
||||
},
|
||||
});
|
||||
|
||||
await assert.rejects(manager.install("/tmp/upgrade.ncpkg"), /runtime unavailable/);
|
||||
assert.deepEqual(activePlugin, { id: pluginId, enabled: false, activeVersion: "1.0.0" });
|
||||
});
|
||||
|
||||
test("failed restoration leaves the previous plugin disabled", async () => {
|
||||
const calls = [];
|
||||
const pluginId = "com.example.upgrade-broken-rollback";
|
||||
let activePlugin = { id: pluginId, enabled: true, activeVersion: "1.0.0" };
|
||||
const manager = new PluginManager({
|
||||
database: {
|
||||
close() {},
|
||||
getActivePlugin: () => ({ ...activePlugin }),
|
||||
listPlugins: () => [],
|
||||
setEnabled(id, enabled) {
|
||||
assert.equal(id, pluginId);
|
||||
activePlugin = { ...activePlugin, enabled };
|
||||
calls.push(`enabled:${enabled}`);
|
||||
},
|
||||
},
|
||||
packageStore: {
|
||||
async initialize() {},
|
||||
async install(_archivePath, options) {
|
||||
await options.beforeActivate({
|
||||
pluginId,
|
||||
version: "2.0.0",
|
||||
previousPlugin: { ...activePlugin },
|
||||
reason: "replace-active-files",
|
||||
});
|
||||
throw new Error("replacement failed");
|
||||
},
|
||||
},
|
||||
runtimeSupervisor: {
|
||||
async startEnabled() {},
|
||||
async stop() { calls.push("stop"); },
|
||||
async start() {
|
||||
calls.push("start:failed");
|
||||
throw new Error("previous package is invalid");
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
await assert.rejects(manager.install("/tmp/upgrade.ncpkg"), /replacement failed/);
|
||||
assert.deepEqual(calls, [
|
||||
"enabled:false",
|
||||
"stop",
|
||||
"enabled:true",
|
||||
"start:failed",
|
||||
"enabled:false",
|
||||
]);
|
||||
assert.equal(activePlugin.enabled, false);
|
||||
});
|
||||
|
||||
test("uninstall disables lazy activation before stopping and removing code", async () => {
|
||||
const calls = [];
|
||||
const pluginId = "com.example.remove";
|
||||
const manager = new PluginManager({
|
||||
database: {
|
||||
close() {},
|
||||
getActivePlugin: () => ({ id: pluginId, enabled: true }),
|
||||
listPlugins: () => [],
|
||||
setEnabled(id, enabled) { calls.push(`enabled:${id}:${enabled}`); },
|
||||
},
|
||||
packageStore: {
|
||||
async initialize() {},
|
||||
async uninstall(id) {
|
||||
calls.push(`uninstall:${id}`);
|
||||
return true;
|
||||
},
|
||||
},
|
||||
runtimeSupervisor: {
|
||||
async startEnabled() {},
|
||||
async stop(id) { calls.push(`stop:${id}`); },
|
||||
},
|
||||
});
|
||||
assert.equal(await manager.uninstall(pluginId), true);
|
||||
assert.deepEqual(calls, [
|
||||
`enabled:${pluginId}:false`,
|
||||
`stop:${pluginId}`,
|
||||
`uninstall:${pluginId}`,
|
||||
]);
|
||||
});
|
||||
|
||||
test("uninstall never removes package code when runtime cleanup fails", async () => {
|
||||
let uninstallCalls = 0;
|
||||
const pluginId = "com.example.contained";
|
||||
const manager = new PluginManager({
|
||||
database: {
|
||||
close() {},
|
||||
getActivePlugin: () => ({ id: pluginId, enabled: true }),
|
||||
listPlugins: () => [],
|
||||
setEnabled() {},
|
||||
},
|
||||
packageStore: {
|
||||
async initialize() {},
|
||||
async uninstall() {
|
||||
uninstallCalls += 1;
|
||||
return true;
|
||||
},
|
||||
},
|
||||
runtimeSupervisor: {
|
||||
async startEnabled() {},
|
||||
async stop() { throw new Error("companion cleanup failed"); },
|
||||
},
|
||||
});
|
||||
|
||||
await assert.rejects(manager.uninstall(pluginId), /companion cleanup failed/);
|
||||
assert.equal(uninstallCalls, 0);
|
||||
});
|
||||
|
||||
test("concurrent shutdown callers share one complete shutdown operation", async () => {
|
||||
let databaseCloses = 0;
|
||||
let supervisorShutdowns = 0;
|
||||
let beforeCloses = 0;
|
||||
const manager = new PluginManager({
|
||||
database: {
|
||||
close() { databaseCloses += 1; },
|
||||
listPlugins: () => [],
|
||||
},
|
||||
packageStore: { async initialize() {} },
|
||||
runtimeSupervisor: {
|
||||
async startEnabled() {},
|
||||
async shutdown() { supervisorShutdowns += 1; },
|
||||
},
|
||||
async beforeClose() { beforeCloses += 1; },
|
||||
});
|
||||
await manager.initialize();
|
||||
|
||||
const first = manager.shutdown();
|
||||
const second = manager.shutdown();
|
||||
assert.equal(first, second);
|
||||
await Promise.all([first, second]);
|
||||
assert.equal(supervisorShutdowns, 1);
|
||||
assert.equal(beforeCloses, 1);
|
||||
assert.equal(databaseCloses, 1);
|
||||
});
|
||||
|
||||
test("shutdown closes the database even when secure-service cleanup fails", async () => {
|
||||
let databaseCloses = 0;
|
||||
const manager = new PluginManager({
|
||||
database: {
|
||||
close() { databaseCloses += 1; },
|
||||
listPlugins: () => [],
|
||||
},
|
||||
packageStore: { async initialize() {} },
|
||||
runtimeSupervisor: { async startEnabled() {}, async shutdown() {} },
|
||||
async beforeClose() { throw new Error("secure cleanup failed"); },
|
||||
});
|
||||
await manager.initialize();
|
||||
await assert.rejects(manager.shutdown(), /secure cleanup failed/);
|
||||
assert.equal(databaseCloses, 1);
|
||||
});
|
||||
|
||||
test("shutdown cancels an in-flight runtime mutation before waiting for its queue", async () => {
|
||||
const calls = [];
|
||||
let rejectStart;
|
||||
let markStartEntered;
|
||||
const startEntered = new Promise((resolve) => { markStartEntered = resolve; });
|
||||
const pluginId = "com.example.shutdown-placement";
|
||||
const manager = new PluginManager({
|
||||
database: {
|
||||
close() { calls.push("database:close"); },
|
||||
getActivePlugin: () => ({
|
||||
id: pluginId,
|
||||
activeVersion: "1.0.0",
|
||||
enabled: false,
|
||||
runtime: { quarantinedAt: null },
|
||||
}),
|
||||
listPlugins: () => [],
|
||||
setEnabled(_id, enabled) { calls.push(`enabled:${enabled}`); },
|
||||
},
|
||||
packageStore: { async initialize() {} },
|
||||
runtimeSupervisor: {
|
||||
async startEnabled() {},
|
||||
start() {
|
||||
calls.push("runtime:start");
|
||||
markStartEntered();
|
||||
return new Promise((_resolve, reject) => { rejectStart = reject; });
|
||||
},
|
||||
async shutdown() {
|
||||
calls.push("runtime:shutdown");
|
||||
rejectStart(new Error("placement cancelled by shutdown"));
|
||||
},
|
||||
},
|
||||
});
|
||||
await manager.initialize();
|
||||
|
||||
const enabling = manager.setEnabled(pluginId, true);
|
||||
await startEntered;
|
||||
const shuttingDown = manager.shutdown();
|
||||
await assert.rejects(enabling, /placement cancelled by shutdown/);
|
||||
await shuttingDown;
|
||||
assert.deepEqual(calls, [
|
||||
"enabled:true",
|
||||
"runtime:start",
|
||||
"runtime:shutdown",
|
||||
"enabled:false",
|
||||
"database:close",
|
||||
]);
|
||||
});
|
||||
Reference in New Issue
Block a user