Files
NetMesh/electron/plugins/terminalDataPipelineService.test.cjs

934 lines
34 KiB
JavaScript
Raw Normal View History

"use strict";
const assert = require("node:assert/strict");
const { MessageChannel } = require("node:worker_threads");
const test = require("node:test");
const { PluginRpcError, RPC_ERRORS } = require("./rpcRouter.cjs");
const { PluginTerminalDataPipelineService } = require("./terminalDataPipelineService.cjs");
function provider(pluginId, id, direction) {
return Object.freeze({
pluginId,
pluginVersion: "1.0.0",
pluginDisplayName: pluginId,
provider: Object.freeze({ id, kind: `terminal.interceptor.${direction}`, label: id }),
});
}
function harness(options = {}) {
const providers = options.providers ?? [provider("com.example", "com.example.input", "input")];
const identity = Object.freeze({
pluginId: "com.example",
pluginVersion: "1.0.0",
runtimeId: "runtime-1",
runtimeKind: options.runtimeKind ?? "utility",
securityPrincipal: "principal-1",
});
const attached = [];
const detached = [];
const authorized = [];
const activationCalls = [];
const revokedSessions = [];
let permissionRevocationListener = null;
const contributionListeners = [];
const runtimeListeners = [];
const contributionService = {
listProviders({ kind }) { return providers.filter((entry) => entry.provider.kind === kind); },
async activateProvider(providerId) {
activationCalls.push(providerId);
await options.onActivateProvider?.();
const entry = providers.find((candidate) => candidate.provider.id === providerId);
if (!entry) throw new Error("missing provider");
return {
plugin: {
id: entry.pluginId,
activeVersion: entry.pluginVersion,
manifest: { main: { node: "dist/index.js" }, permissions: { required: ["runtime.advanced"] } },
},
provider: entry.provider,
identity: options.activationIdentity ?? identity,
};
},
onDidChange(listener) { contributionListeners.push(listener); },
};
const runtimeSupervisor = {
getRuntimeIdentity() { return options.getRuntimeIdentity ? options.getRuntimeIdentity() : identity; },
onDidChangeRuntime(listener) { runtimeListeners.push(listener); },
async attachTerminalInterceptor(pluginId, descriptor, port, attachOptions) {
port.unref?.();
attached.push({ side: "plugin", pluginId, descriptor, port, attachOptions });
await options.onAttachTerminalInterceptor?.();
},
};
const permissionEngine = {
async authorize(context, request) {
authorized.push({ context, request });
await options.onAuthorize?.({ context, request, providers });
request.validateBeforeGrant?.();
const scope = typeof options.permissionScope === "function"
? options.permissionScope({ context, request, call: authorized.length })
: options.permissionScope ?? "session";
return { scope };
},
revokeSession(sessionId) { revokedSessions.push(sessionId); },
onDidRevoke(listener) {
permissionRevocationListener = listener;
return { dispose: () => { permissionRevocationListener = null; } };
},
};
const worker = {
warningListener: null,
ownedListener: null,
closedListener: null,
ownsSession() { return true; },
getSessionOwnerWebContentsId() {
return options.sessionOwnerWebContentsId ?? null;
},
attachTerminalInterceptor(descriptor, port) {
port.unref?.();
attached.push({ side: "worker", descriptor, port });
},
detachTerminalInterceptor(sessionId, direction) { detached.push({ sessionId, direction }); },
onTerminalInterceptorWarning(listener) {
this.warningListener = listener;
return { dispose: () => { this.warningListener = null; } };
},
onSessionOwned(listener) {
this.ownedListener = listener;
return { dispose: () => { this.ownedListener = null; } };
},
onSessionClosed(listener) {
this.closedListener = listener;
return { dispose: () => { this.closedListener = null; } };
},
};
const selections = [];
const warnings = [];
const service = new PluginTerminalDataPipelineService({
contributionService,
permissionEngine,
runtimeSupervisor,
MessageChannelMain: MessageChannel,
requestSelection: async (request) => {
selections.push(request);
return Object.hasOwn(options, "selectedProviderId")
? options.selectedProviderId
: request.providers[0].provider.id;
},
showWarning: (warning) => warnings.push(warning),
});
service.bindTerminalWorkerManager(worker);
return {
service,
worker,
identity,
attached,
detached,
authorized,
activationCalls,
revokedSessions,
selections,
warnings,
runtimeListeners,
revokePermission(event) { permissionRevocationListener?.(event); },
contributionListeners,
contributionService,
};
}
const session = Object.freeze({
sessionId: "session-1",
protocol: "ssh",
status: "connected",
});
test("pipeline activation requires exact session permissions and transfers one port to each process", async () => {
const h = harness();
const result = await h.service.configureDirection(session, "input");
assert.deepEqual(result, {
status: "active",
direction: "input",
providerId: "com.example.input",
pluginId: "com.example",
});
assert.deepEqual(h.authorized.map((entry) => entry.request.permission), [
"provider.terminal",
"terminal.intercept.input",
]);
assert.ok(h.authorized.every((entry) => entry.request.sessionId === "session-1"));
assert.ok(h.authorized.every((entry) => (
JSON.stringify(entry.request.allowedScopes) === JSON.stringify(["session", "application", "always"])
)));
assert.deepEqual(h.attached.map((entry) => entry.side), ["plugin", "worker"]);
assert.deepEqual(h.attached[0].attachOptions.expectedIdentity, h.identity);
assert.deepEqual(h.attached[0].descriptor, {
providerId: "com.example.input",
direction: "input",
session,
});
assert.deepEqual(h.attached[1].descriptor, {
sessionId: "session-1",
direction: "input",
providerId: "com.example.input",
pluginId: "com.example",
pluginVersion: "1.0.0",
runtimeId: "runtime-1",
runtimeKind: "utility",
securityPrincipal: "principal-1",
session,
});
});
test("pipeline rejects one-use permission grants before opening a streaming port", async () => {
const h = harness({ permissionScope: "once" });
await assert.rejects(
() => h.service.configureDirection(session, "input"),
/require a session, application, or persistent permission grant/,
);
assert.equal(h.authorized.length, 1);
assert.equal(h.attached.length, 0);
const laterOnce = harness({
permissionScope: ({ call }) => (call === 2 ? "once" : "session"),
});
await assert.rejects(
() => laterOnce.service.configureDirection(session, "input"),
/require a session, application, or persistent permission grant/,
);
assert.equal(laterOnce.authorized.length, 2);
assert.equal(laterOnce.attached.length, 0);
});
test("permission denial is remembered for the session instead of prompting again", async () => {
const h = harness({
onAuthorize() {
throw new PluginRpcError(RPC_ERRORS.permissionDenied, "permission denied");
},
});
await assert.rejects(
() => h.service.configureDirection(session, "input"),
(error) => error?.code === RPC_ERRORS.permissionDenied,
);
h.contributionListeners[0]();
assert.equal((await h.service.configureDirection(session, "input")).status, "declined");
assert.equal(h.authorized.length, 1);
assert.equal(h.attached.length, 0);
});
test("session event permission denial is a quiet declined result", async () => {
const h = harness({
onAuthorize() {
throw new PluginRpcError(RPC_ERRORS.permissionDenied, "permission denied");
},
});
const results = await h.service.handleSessionEvent({ type: "connected", session }, {
webContentsId: 99,
});
assert.deepEqual(results, [
{ status: "declined", direction: "input" },
{ status: "none", direction: "output" },
]);
assert.deepEqual(h.warnings, []);
});
test("pipeline accepts an existing long-lived permission grant", async () => {
const h = harness({ permissionScope: "existing" });
const result = await h.service.configureDirection(session, "input");
assert.equal(result.status, "active");
assert.equal(h.authorized.length, 2);
assert.deepEqual(h.attached.map((entry) => entry.side), ["plugin", "worker"]);
});
test("multiple interceptors require an explicit per-session selection", async () => {
const h = harness({
providers: [
provider("com.example", "com.example.input", "input"),
provider("com.other", "com.other.input", "input"),
],
selectedProviderId: "com.example.input",
});
await h.service.configureDirection(session, "input");
assert.equal(h.selections.length, 1);
assert.equal(h.selections[0].providers.length, 2);
await h.service.configureDirection(session, "input");
assert.equal(h.selections.length, 1, "an active session binding must not prompt again");
});
test("declining competing interceptors is remembered for the session and reset on disposal", async () => {
const h = harness({
providers: [
provider("com.example", "com.example.input", "input"),
provider("com.other", "com.other.input", "input"),
],
selectedProviderId: null,
});
assert.equal((await h.service.configureDirection(session, "input")).status, "declined");
assert.equal((await h.service.configureDirection(session, "input")).status, "declined");
assert.equal(h.selections.length, 1);
await h.service.handleSessionEvent({
type: "disposed",
session: { ...session, status: "disconnected" },
});
await h.service.configureDirection(session, "input");
assert.equal(h.selections.length, 2);
});
test("concurrent snapshots serialize to one authorization and one port pair", async () => {
const h = harness();
const [first, second] = await Promise.all([
h.service.configureDirection(session, "input"),
h.service.configureDirection(session, "input"),
]);
assert.equal(first.status, "active");
assert.equal(second.status, "active");
assert.equal(h.authorized.length, 2);
assert.equal(h.attached.length, 2);
});
test("browser runtimes cannot receive privileged terminal ports", async () => {
const h = harness({ runtimeKind: "browser" });
await assert.rejects(
() => h.service.configureDirection(session, "input"),
/advanced utility runtime/,
);
assert.equal(h.attached.length, 0);
});
test("stale activation identity fails before permissions or port transfer", async () => {
const h = harness({
activationIdentity: {
pluginId: "com.example",
pluginVersion: "0.9.0",
runtimeId: "stale-runtime",
runtimeKind: "utility",
securityPrincipal: "principal-1",
},
});
await assert.rejects(
() => h.service.configureDirection(session, "input"),
/identity is unavailable, stale/,
);
assert.equal(h.authorized.length, 0);
assert.equal(h.attached.length, 0);
});
test("worker transfer starts only after the plugin port is ready", async () => {
const h = harness();
h.worker.attachTerminalInterceptor = () => { throw new Error("worker unavailable"); };
await assert.rejects(
() => h.service.configureDirection(session, "input"),
/worker unavailable/,
);
assert.deepEqual(h.attached.map((entry) => entry.side), ["plugin"]);
});
test("contribution withdrawal during authorization prevents stale port publication", async () => {
let withdrawn = false;
const h = harness({
onAuthorize({ providers }) {
if (withdrawn) return;
withdrawn = true;
providers.splice(0, providers.length);
},
});
await assert.rejects(
() => h.service.configureDirection(session, "input"),
/contribution changed/,
);
assert.equal(h.attached.length, 0);
});
test("runtime replacement during port attachment cannot publish a stale active binding", async () => {
let runtimeId = "runtime-1";
const h = harness({
getRuntimeIdentity: () => ({
pluginId: "com.example",
pluginVersion: "1.0.0",
runtimeId,
runtimeKind: "utility",
securityPrincipal: "principal-1",
}),
onAttachTerminalInterceptor() { runtimeId = "runtime-2"; },
});
await assert.rejects(
() => h.service.configureDirection(session, "input"),
/runtime changed during port attachment/,
);
assert.deepEqual(h.attached.map((entry) => entry.side), ["plugin"]);
assert.deepEqual(h.detached, []);
assert.equal(h.service.active.size, 0);
});
test("disconnect detaches and reconnect restores the remembered session interceptor", async () => {
const h = harness({
providers: [
provider("com.example", "com.example.input", "input"),
provider("com.other", "com.other.input", "input"),
],
selectedProviderId: "com.example.input",
});
await h.service.handleSessionEvent({ type: "connected", session });
assert.equal(h.selections.length, 1);
assert.deepEqual(h.attached.map((entry) => entry.side), ["plugin", "worker"]);
await h.service.handleSessionEvent({
type: "disconnected",
session: { ...session, status: "disconnected" },
}, { webContentsId: 99 });
assert.deepEqual(h.detached, [{ sessionId: "session-1", direction: "input" }]);
await h.worker.ownedListener({ sessionId: session.sessionId, webContentsId: 99 });
assert.equal(h.selections.length, 1, "reconnect ownership must reuse the session-local choice");
assert.deepEqual(h.attached.map((entry) => entry.side), [
"plugin", "worker", "plugin", "worker",
]);
await h.service.handleSessionEvent({ type: "reconnected", session });
assert.equal(h.selections.length, 1, "reconnect must reuse the session-local choice");
assert.deepEqual(h.attached.map((entry) => entry.side), [
"plugin", "worker", "plugin", "worker", "plugin", "worker",
]);
assert.equal(h.attached.at(-1).descriptor.session.status, "connected");
});
test("session metadata changes refresh the interceptor invocation snapshot", async () => {
const h = harness();
await h.service.handleSessionEvent({ type: "connected", session });
await h.service.handleSessionEvent({
type: "cwdChanged",
session: { ...session, cwd: "/srv/current" },
});
assert.equal(h.attached.at(-1).descriptor.session.cwd, "/srv/current");
assert.deepEqual(h.attached.map((entry) => entry.side), [
"plugin", "worker", "plugin", "worker",
]);
assert.deepEqual(h.detached, [], "snapshot refresh must replace the worker port atomically");
});
test("revoking a terminal permission immediately detaches the matching active interceptor", async () => {
const h = harness();
await h.service.configureDirection(session, "input");
h.revokePermission({
pluginId: "com.example",
permission: "terminal.intercept.input",
resource: "*",
scope: "application",
});
assert.equal(h.service.active.size, 0);
assert.deepEqual(h.detached, [{ sessionId: "session-1", direction: "input" }]);
});
test("a worker-confirmed terminal exit revokes session grants and clears pipeline state", async () => {
const h = harness();
await h.service.configureDirection(session, "input");
h.worker.closedListener({ sessionId: session.sessionId, reason: "exited" });
assert.deepEqual(h.revokedSessions, [session.sessionId]);
assert.equal(h.service.active.size, 0);
assert.deepEqual(h.detached, [{ sessionId: session.sessionId, direction: "input" }]);
});
test("a renderer cannot attach an interceptor to another window's terminal session", async () => {
const h = harness();
h.worker.ownsSession = () => false;
await assert.rejects(
() => h.service.configureDirection(session, "input", { webContentsId: 99 }),
/not owned by the requesting window/,
);
assert.equal(h.authorized.length, 0);
assert.equal(h.attached.length, 0);
});
test("renderer loss during provider selection prevents authorization", async () => {
const h = harness({
providers: [
provider("com.example", "com.example.input", "input"),
provider("com.other", "com.other.input", "input"),
],
});
let owned = true;
let releaseSelection;
h.worker.ownsSession = () => owned;
h.service.requestSelection = async () => {
await new Promise((resolve) => { releaseSelection = resolve; });
return "com.example.input";
};
const activation = h.service.configureDirection(session, "input", { webContentsId: 99 });
while (!releaseSelection) await new Promise((resolve) => setImmediate(resolve));
owned = false;
releaseSelection();
await assert.rejects(() => activation, /ownership changed during selection/);
assert.equal(h.authorized.length, 0);
assert.equal(h.attached.length, 0);
});
test("renderer loss during permission approval prevents a lasting grant", async () => {
let owned = true;
let releaseAuthorization;
const h = harness({
onAuthorize: () => new Promise((resolve) => { releaseAuthorization = resolve; }),
});
h.worker.ownsSession = () => owned;
const activation = h.service.configureDirection(session, "input", { webContentsId: 99 });
while (!releaseAuthorization) await new Promise((resolve) => setImmediate(resolve));
owned = false;
releaseAuthorization();
await assert.rejects(() => activation, /ownership changed during activation/);
assert.equal(h.authorized.length, 1);
assert.equal(h.attached.length, 0);
});
test("renderer loss during port attachment prevents worker publication", async () => {
let owned = true;
let releaseAttach;
const h = harness({
onAttachTerminalInterceptor: () => new Promise((resolve) => { releaseAttach = resolve; }),
});
h.worker.ownsSession = () => owned;
const activation = h.service.configureDirection(session, "input", { webContentsId: 99 });
while (!releaseAttach) await new Promise((resolve) => setImmediate(resolve));
owned = false;
releaseAttach();
await assert.rejects(() => activation, /ownership changed during activation/);
assert.deepEqual(h.attached.map((entry) => entry.side), ["plugin"]);
});
test("created events defer silently until the worker has recorded session ownership", async () => {
const h = harness();
let owned = false;
h.worker.ownsSession = () => owned;
const results = await h.service.handleSessionEvent({ type: "created", session }, {
webContentsId: 99,
});
assert.deepEqual(results, [
{ status: "pending-session", direction: "input" },
{ status: "pending-session", direction: "output" },
]);
assert.equal(h.authorized.length, 0);
assert.equal(h.attached.length, 0);
owned = true;
await h.worker.ownedListener({ sessionId: session.sessionId, webContentsId: 99 });
assert.deepEqual(h.attached.map((entry) => entry.side), ["plugin", "worker"]);
assert.equal(h.authorized.length, 2);
});
test("session disposal invalidates an in-flight lazy activation before port transfer", async () => {
const h = harness();
const originalActivate = h.contributionService.activateProvider;
let releaseActivation;
h.contributionService.activateProvider = async (...args) => {
await new Promise((resolve) => { releaseActivation = resolve; });
return originalActivate(...args);
};
const pending = h.service.configureDirection(session, "input");
await new Promise((resolve) => setImmediate(resolve));
await h.service.handleSessionEvent({
type: "disposed",
session: { ...session, status: "disconnected" },
});
releaseActivation();
await assert.rejects(pending, /session changed/);
assert.equal(h.attached.length, 0);
});
test("a stale renderer disposal cannot detach the current owner's interceptor", async () => {
const h = harness({ sessionOwnerWebContentsId: 9 });
await h.service.configureDirection(session, "input", { webContentsId: 9 });
await h.service.handleSessionEvent({
type: "disposed",
session: { ...session, status: "disconnected" },
}, { webContentsId: 7 });
assert.equal(h.service.active.size, 1);
assert.deepEqual(h.detached, []);
assert.deepEqual(h.revokedSessions, []);
await h.service.handleSessionEvent({
type: "disposed",
session: { ...session, status: "disconnected" },
}, { webContentsId: 9 });
assert.equal(h.service.active.size, 0);
assert.deepEqual(h.detached, [{ sessionId: "session-1", direction: "input" }]);
assert.deepEqual(h.revokedSessions, ["session-1"]);
});
test("a stale renderer cannot publish any lifecycle event for the current owner's session", async () => {
const h = harness({ sessionOwnerWebContentsId: 9 });
await h.service.configureDirection(session, "input", { webContentsId: 9 });
for (const type of ["created", "disconnected", "connected", "cwdChanged"]) {
const result = await h.service.handleSessionEvent({ type, session }, { webContentsId: 7 });
assert.deepEqual(result, []);
}
assert.equal(h.service.active.size, 1);
assert.deepEqual(h.detached, []);
assert.deepEqual(h.revokedSessions, []);
});
test("runtime failure statuses quarantine both directions before detaching", async () => {
for (const status of ["error", "quarantined"]) {
const h = harness({
providers: [
provider("com.example", "com.example.input", "input"),
provider("com.example", "com.example.output", "output"),
],
});
await h.service.handleSessionEvent({ type: "connected", session });
h.runtimeListeners[0]({
status,
pluginId: "com.example",
runtimeId: "runtime-1",
error: "utility runtime crashed",
});
assert.deepEqual(h.detached.map((entry) => entry.direction).sort(), ["input", "output"]);
assert.deepEqual(h.warnings.map((warning) => warning.code).sort(), [
`runtime-${status}`,
`runtime-${status}`,
]);
const reconnect = await h.service.handleSessionEvent({ type: "connected", session });
assert.deepEqual(reconnect.map((result) => result.status), ["declined", "declined"]);
assert.equal(h.authorized.length, 4, "a crashed runtime must not be authorized again");
assert.equal(h.attached.length, 4, "a crashed runtime must not be reattached");
await h.service.handleSessionEvent({
type: "disposed",
session: { ...session, status: "disconnected" },
});
}
});
test("runtime exit clears the cached provider choice but ordinary reconnect preserves it", async () => {
const providers = [
provider("com.example", "com.example.input", "input"),
provider("com.other", "com.other.input", "input"),
];
const h = harness({
providers,
selectedProviderId: "com.example.input",
});
await h.service.configureDirection(session, "input");
assert.equal(h.selections.length, 1);
const withdrawn = providers.splice(0);
h.runtimeListeners[0]({
status: "stopped",
pluginId: "com.example",
runtimeId: "runtime-1",
});
providers.push(...withdrawn);
await h.service.configureDirection(session, "input");
assert.equal(h.selections.length, 2, "a stopped runtime must discard its session-local choice");
});
test("an expected runtime close waits for the stopped event instead of quarantining", async () => {
let currentIdentity;
const h = harness({ getRuntimeIdentity: () => currentIdentity });
currentIdentity = h.identity;
assert.equal((await h.service.configureDirection(session, "input")).status, "active");
currentIdentity = null;
h.worker.warningListener({
sessionId: session.sessionId,
direction: "input",
code: "closed",
message: "Terminal interceptor stopped and was disabled",
});
assert.equal(h.warnings.length, 0);
h.runtimeListeners[0]({
status: "stopped",
pluginId: "com.example",
runtimeId: "runtime-1",
});
currentIdentity = h.identity;
assert.equal((await h.service.configureDirection(session, "input")).status, "active");
assert.equal(h.warnings.length, 0);
assert.equal(h.authorized.length, 4);
assert.equal(h.attached.length, 4);
});
test("a close during runtime failure is quarantined by the authoritative error event", async () => {
let currentIdentity;
const h = harness({ getRuntimeIdentity: () => currentIdentity });
currentIdentity = h.identity;
assert.equal((await h.service.configureDirection(session, "input")).status, "active");
currentIdentity = null;
h.worker.warningListener({
sessionId: session.sessionId,
direction: "input",
code: "closed",
message: "Terminal interceptor stopped and was disabled",
});
h.runtimeListeners[0]({
status: "error",
pluginId: "com.example",
runtimeId: "runtime-1",
error: "utility runtime crashed",
});
const [result] = await h.service.handleSessionEvent({ type: "connected", session });
assert.equal(result.status, "declined");
assert.deepEqual(h.warnings.map((warning) => warning.code), ["runtime-error"]);
assert.equal(h.authorized.length, 2);
assert.equal(h.attached.length, 2);
});
test("terminal worker exit invalidates active bindings before worker restart", async () => {
const h = harness();
await h.service.configureDirection(session, "input");
h.worker.warningListener({ code: "worker-exit", message: "Terminal worker exited" });
await h.service.configureDirection(session, "input");
assert.equal(h.authorized.length, 4);
assert.equal(h.attached.length, 4);
});
test("a failed interceptor stays quarantined for the rest of the session", async () => {
const h = harness();
assert.equal((await h.service.configureDirection(session, "input")).status, "active");
h.worker.warningListener({
sessionId: session.sessionId,
direction: "input",
code: "timeout",
message: "Interceptor timed out",
});
const [result] = await h.service.handleSessionEvent({ type: "connected", session });
assert.equal(result.status, "declined");
assert.equal(h.authorized.length, 2, "the quarantined provider must not be authorized again");
assert.equal(h.attached.length, 2, "the quarantined provider must not be reattached");
assert.equal(h.warnings.length, 1);
});
test("permission revocation while a port is attaching prevents the worker attachment", async () => {
let releaseAttach;
const attachGate = new Promise((resolve) => { releaseAttach = resolve; });
const h = harness({ onAttachTerminalInterceptor: () => attachGate });
const activation = h.service.configureDirection(session, "input");
while (h.attached.length === 0) await new Promise((resolve) => setImmediate(resolve));
h.revokePermission({ pluginId: "com.example", permission: "terminal.intercept.input" });
releaseAttach();
await assert.rejects(
() => activation,
(error) => error?.code === RPC_ERRORS.permissionDenied,
);
assert.deepEqual(h.attached.map((entry) => entry.side), ["plugin"]);
});
test("a concurrent metadata refresh cannot revive a quarantined interceptor", async () => {
let attachCalls = 0;
let releaseRefresh;
const refreshGate = new Promise((resolve) => { releaseRefresh = resolve; });
const h = harness({
onAttachTerminalInterceptor() {
attachCalls += 1;
return attachCalls === 2 ? refreshGate : undefined;
},
});
assert.equal((await h.service.configureDirection(session, "input")).status, "active");
const refresh = h.service.configureDirection({ ...session, title: "new title" }, "input");
while (h.attached.length < 3) await new Promise((resolve) => setImmediate(resolve));
h.worker.warningListener({
sessionId: session.sessionId,
direction: "input",
code: "timeout",
message: "Interceptor timed out",
});
releaseRefresh();
await assert.rejects(
() => refresh,
(error) => error?.code === RPC_ERRORS.unavailable,
);
assert.deepEqual(h.attached.map((entry) => entry.side), ["plugin", "worker", "plugin"]);
assert.equal((await h.service.configureDirection(session, "input")).status, "declined");
});
test("a runtime crash reported after its first port attachment fails still quarantines the interceptor", async () => {
let rejectAttach;
const attachGate = new Promise((_resolve, reject) => { rejectAttach = reject; });
const h = harness({ onAttachTerminalInterceptor: () => attachGate });
const activation = h.service.configureDirection(session, "input");
while (h.attached.length === 0) await new Promise((resolve) => setImmediate(resolve));
rejectAttach(new Error("plugin runtime transport closed"));
await assert.rejects(() => activation, /transport closed/);
h.runtimeListeners[0]({
status: "error",
pluginId: "com.example",
runtimeId: h.identity.runtimeId,
error: "utility runtime crashed during attachment",
});
assert.deepEqual(h.attached.map((entry) => entry.side), ["plugin"]);
assert.equal((await h.service.configureDirection(session, "input")).status, "declined");
assert.equal(h.authorized.length, 2);
assert.deepEqual(h.warnings.map((warning) => warning.code), ["runtime-error"]);
});
test("a runtime crash during first provider activation prevents automatic retry", async () => {
let rejectActivation;
const activationGate = new Promise((_resolve, reject) => { rejectActivation = reject; });
const h = harness({ onActivateProvider: () => activationGate });
const activation = h.service.configureDirection(session, "input");
while (h.activationCalls.length === 0) await new Promise((resolve) => setImmediate(resolve));
h.runtimeListeners[0]({
status: "error",
pluginId: "com.example",
runtimeId: h.identity.runtimeId,
error: "utility runtime crashed during activation",
});
rejectActivation(new Error("plugin activation transport closed"));
await assert.rejects(() => activation, /transport closed/);
assert.equal((await h.service.configureDirection(session, "input")).status, "declined");
assert.equal(h.activationCalls.length, 1);
assert.equal(h.authorized.length, 0);
assert.deepEqual(h.warnings.map((warning) => warning.code), ["runtime-error"]);
});
test("a late crash from an old session generation cannot quarantine a reused session id", async () => {
let rejectOldActivation;
let activationCalls = 0;
const oldActivationGate = new Promise((_resolve, reject) => { rejectOldActivation = reject; });
const h = harness({
onActivateProvider() {
activationCalls += 1;
return activationCalls === 1 ? oldActivationGate : undefined;
},
});
const oldActivation = h.service.handleSessionEvent({ type: "snapshot", session });
while (h.activationCalls.length === 0) await new Promise((resolve) => setImmediate(resolve));
await h.service.handleSessionEvent({ type: "disposed", session });
const newActivation = h.service.handleSessionEvent({ type: "created", session });
h.runtimeListeners[0]({
status: "error",
pluginId: "com.example",
runtimeId: h.identity.runtimeId,
error: "old utility runtime crashed late",
});
rejectOldActivation(new Error("old plugin activation transport closed"));
const oldResult = await oldActivation;
const newResult = await newActivation;
assert.equal(oldResult[0].status, "cancelled");
assert.notEqual(newResult[0].status, "declined");
assert.equal(h.activationCalls.length, 2);
assert.deepEqual(h.warnings.filter((warning) => warning.code === "runtime-error"), []);
});
test("a normal stop before provider activation rejects cannot become a later crash record", async () => {
let rejectActivation;
let activationCalls = 0;
const activationGate = new Promise((_resolve, reject) => { rejectActivation = reject; });
const h = harness({
onActivateProvider() {
activationCalls += 1;
return activationCalls === 1 ? activationGate : undefined;
},
});
const first = h.service.configureDirection(session, "input");
while (h.activationCalls.length === 0) await new Promise((resolve) => setImmediate(resolve));
h.runtimeListeners[0]({
status: "stopped",
pluginId: "com.example",
runtimeId: h.identity.runtimeId,
});
rejectActivation(new Error("plugin stopped during activation"));
await assert.rejects(
() => first,
(error) => error?.code === RPC_ERRORS.cancelled,
);
h.runtimeListeners[0]({
status: "error",
pluginId: "com.example",
runtimeId: h.identity.runtimeId,
error: "unrelated later crash",
});
assert.equal((await h.service.configureDirection(session, "input")).status, "active");
assert.equal(h.activationCalls.length, 2);
assert.deepEqual(h.warnings, []);
});
test("normal cancellation and an already-reported crash do not emit duplicate activation warnings", async () => {
let rejectActivation;
const activationGate = new Promise((_resolve, reject) => { rejectActivation = reject; });
const normal = harness({ onActivateProvider: () => activationGate });
const normalEvent = normal.service.handleSessionEvent({ type: "snapshot", session });
while (normal.activationCalls.length === 0) await new Promise((resolve) => setImmediate(resolve));
normal.runtimeListeners[0]({
status: "stopped",
pluginId: "com.example",
runtimeId: normal.identity.runtimeId,
});
rejectActivation(new Error("runtime stopped"));
const normalResults = await normalEvent;
assert.equal(normalResults[0].status, "cancelled");
assert.deepEqual(normal.warnings, []);
let rejectCrash;
const crashGate = new Promise((_resolve, reject) => { rejectCrash = reject; });
const crashed = harness({ onActivateProvider: () => crashGate });
const crashEvent = crashed.service.handleSessionEvent({ type: "snapshot", session });
while (crashed.activationCalls.length === 0) await new Promise((resolve) => setImmediate(resolve));
crashed.runtimeListeners[0]({
status: "error",
pluginId: "com.example",
runtimeId: crashed.identity.runtimeId,
error: "runtime crashed",
});
rejectCrash(new Error("transport closed"));
const crashResults = await crashEvent;
assert.equal(crashResults[0].status, "cancelled");
assert.deepEqual(crashed.warnings.map((warning) => warning.code), ["runtime-error"]);
});
test("contribution changes do not clear a failed interceptor quarantine", async () => {
const h = harness();
assert.equal((await h.service.configureDirection(session, "input")).status, "active");
h.worker.warningListener({
sessionId: session.sessionId,
direction: "input",
code: "protocol",
message: "Interceptor returned an invalid frame",
});
h.contributionListeners[0]();
const [result] = await h.service.handleSessionEvent({ type: "snapshot", session });
assert.equal(result.status, "declined");
assert.equal(h.authorized.length, 2);
assert.equal(h.attached.length, 2);
});
test("disposed terminal sessions do not retain lifecycle generations", async () => {
const h = harness({ providers: [] });
for (let index = 0; index < 2_000; index += 1) {
const nextSession = {
...session,
sessionId: `disposed-session-${index}`,
};
await h.service.handleSessionEvent({ type: "created", session: nextSession });
await h.service.handleSessionEvent({ type: "disposed", session: nextSession });
}
assert.equal(h.service._getSessionEpochCountForTests(), 0);
});