From 22528af34da1ff9c4a0a24b2eca9b58f0927f898 Mon Sep 17 00:00:00 2001 From: Vincent Koc Date: Thu, 19 Mar 2026 11:58:43 -0700 Subject: [PATCH] test(ci): trim gateway plugin harness churn --- src/gateway/server-plugins.test.ts | 94 ++++++++++++++++-------------- 1 file changed, 50 insertions(+), 44 deletions(-) diff --git a/src/gateway/server-plugins.test.ts b/src/gateway/server-plugins.test.ts index c1df98dfde2..7bb0fb20f06 100644 --- a/src/gateway/server-plugins.test.ts +++ b/src/gateway/server-plugins.test.ts @@ -1,4 +1,4 @@ -import { afterEach, beforeEach, describe, expect, test, vi } from "vitest"; +import { afterEach, beforeAll, beforeEach, describe, expect, test, vi } from "vitest"; import type { PluginRegistry } from "../plugins/registry.js"; import type { PluginRuntimeGatewayRequestScope } from "../plugins/runtime/gateway-request-scope.js"; import type { PluginRuntime } from "../plugins/runtime/types.js"; @@ -63,6 +63,14 @@ const createRegistry = (diagnostics: PluginDiagnostic[]): PluginRegistry => ({ }); type ServerPluginsModule = typeof import("./server-plugins.js"); +type PluginRuntimeModule = typeof import("../plugins/runtime/index.js"); +type GatewayRequestScopeModule = typeof import("../plugins/runtime/gateway-request-scope.js"); +type MethodScopesModule = typeof import("./method-scopes.js"); + +let serverPluginsModule: ServerPluginsModule; +let runtimeModule: PluginRuntimeModule; +let gatewayRequestScopeModule: GatewayRequestScopeModule; +let methodScopesModule: MethodScopesModule; function createTestContext(label: string): GatewayRequestContext { return { label } as unknown as GatewayRequestContext; @@ -84,8 +92,11 @@ function getLastDispatchedClientScopes(): string[] { return Array.isArray(scopes) ? scopes : []; } -async function importServerPluginsModule(): Promise { - return import("./server-plugins.js"); +async function loadTestModules() { + serverPluginsModule = await import("./server-plugins.js"); + runtimeModule = await import("../plugins/runtime/index.js"); + gatewayRequestScopeModule = await import("../plugins/runtime/gateway-request-scope.js"); + methodScopesModule = await import("./method-scopes.js"); } async function createSubagentRuntime( @@ -112,15 +123,23 @@ async function createSubagentRuntime( if (call?.runtimeOptions?.allowGatewaySubagentBinding !== true) { throw new Error("Expected loadGatewayPlugins to opt into gateway subagent binding"); } - const runtimeModule = await import("../plugins/runtime/index.js"); return runtimeModule.createPluginRuntime({ allowGatewaySubagentBinding: true }).subagent; } -beforeEach(async () => { +async function reloadServerPluginsModule(): Promise { + vi.resetModules(); + await loadTestModules(); + return serverPluginsModule; +} + +beforeAll(async () => { + await loadTestModules(); +}); + +beforeEach(() => { loadOpenClawPlugins.mockReset(); primeConfiguredBindingRegistry.mockClear().mockReturnValue({ bindingCount: 0, channelCount: 0 }); handleGatewayRequest.mockReset(); - const runtimeModule = await import("../plugins/runtime/index.js"); runtimeModule.clearGatewaySubagentRuntime(); handleGatewayRequest.mockImplementation(async (opts: HandleGatewayRequestOptions) => { switch (opts.req.method) { @@ -142,15 +161,13 @@ beforeEach(async () => { }); }); -afterEach(async () => { - const runtimeModule = await import("../plugins/runtime/index.js"); +afterEach(() => { runtimeModule.clearGatewaySubagentRuntime(); - vi.resetModules(); }); describe("loadGatewayPlugins", () => { test("logs plugin errors with details", async () => { - const { loadGatewayPlugins } = await importServerPluginsModule(); + const { loadGatewayPlugins } = serverPluginsModule; const diagnostics: PluginDiagnostic[] = [ { level: "error", @@ -183,7 +200,7 @@ describe("loadGatewayPlugins", () => { }); test("provides subagent runtime with sessions.get method aliases", async () => { - const { loadGatewayPlugins } = await importServerPluginsModule(); + const { loadGatewayPlugins } = serverPluginsModule; loadOpenClawPlugins.mockReturnValue(createRegistry([])); const log = { @@ -205,7 +222,6 @@ describe("loadGatewayPlugins", () => { | { runtimeOptions?: { allowGatewaySubagentBinding?: boolean } } | undefined; expect(call?.runtimeOptions?.allowGatewaySubagentBinding).toBe(true); - const runtimeModule = await import("../plugins/runtime/index.js"); const subagent = runtimeModule.createPluginRuntime({ allowGatewaySubagentBinding: true, }).subagent; @@ -214,9 +230,8 @@ describe("loadGatewayPlugins", () => { }); test("forwards provider and model overrides when the request scope is authorized", async () => { - const serverPlugins = await importServerPluginsModule(); + const serverPlugins = serverPluginsModule; const runtime = await createSubagentRuntime(serverPlugins); - const gatewayScopeModule = await import("../plugins/runtime/gateway-request-scope.js"); const scope = { context: createTestContext("request-scope-forward-overrides"), client: { @@ -227,7 +242,7 @@ describe("loadGatewayPlugins", () => { isWebchatConnect: () => false, } satisfies PluginRuntimeGatewayRequestScope; - await gatewayScopeModule.withPluginRuntimeGatewayRequestScope(scope, () => + await gatewayRequestScopeModule.withPluginRuntimeGatewayRequestScope(scope, () => runtime.run({ sessionKey: "s-override", message: "use the override", @@ -247,7 +262,7 @@ describe("loadGatewayPlugins", () => { }); test("rejects provider/model overrides for fallback runs without explicit authorization", async () => { - const serverPlugins = await importServerPluginsModule(); + const serverPlugins = serverPluginsModule; const runtime = await createSubagentRuntime(serverPlugins); serverPlugins.setFallbackGatewayContext(createTestContext("fallback-deny-overrides")); @@ -265,7 +280,7 @@ describe("loadGatewayPlugins", () => { }); test("allows trusted fallback provider/model overrides when plugin config is explicit", async () => { - const serverPlugins = await importServerPluginsModule(); + const serverPlugins = serverPluginsModule; const runtime = await createSubagentRuntime(serverPlugins, { plugins: { entries: { @@ -279,9 +294,7 @@ describe("loadGatewayPlugins", () => { }, }); serverPlugins.setFallbackGatewayContext(createTestContext("fallback-trusted-overrides")); - const gatewayScopeModule = await import("../plugins/runtime/gateway-request-scope.js"); - - await gatewayScopeModule.withPluginRuntimePluginIdScope("voice-call", () => + await gatewayRequestScopeModule.withPluginRuntimePluginIdScope("voice-call", () => runtime.run({ sessionKey: "s-trusted-override", message: "use trusted override", @@ -299,13 +312,12 @@ describe("loadGatewayPlugins", () => { }); test("includes docs guidance when a plugin fallback override is not trusted", async () => { - const serverPlugins = await importServerPluginsModule(); + const serverPlugins = serverPluginsModule; const runtime = await createSubagentRuntime(serverPlugins); serverPlugins.setFallbackGatewayContext(createTestContext("fallback-untrusted-plugin")); - const gatewayScopeModule = await import("../plugins/runtime/gateway-request-scope.js"); await expect( - gatewayScopeModule.withPluginRuntimePluginIdScope("voice-call", () => + gatewayRequestScopeModule.withPluginRuntimePluginIdScope("voice-call", () => runtime.run({ sessionKey: "s-untrusted-override", message: "use untrusted override", @@ -320,7 +332,7 @@ describe("loadGatewayPlugins", () => { }); test("allows trusted fallback model-only overrides when the model ref is canonical", async () => { - const serverPlugins = await importServerPluginsModule(); + const serverPlugins = serverPluginsModule; const runtime = await createSubagentRuntime(serverPlugins, { plugins: { entries: { @@ -334,9 +346,7 @@ describe("loadGatewayPlugins", () => { }, }); serverPlugins.setFallbackGatewayContext(createTestContext("fallback-model-only-override")); - const gatewayScopeModule = await import("../plugins/runtime/gateway-request-scope.js"); - - await gatewayScopeModule.withPluginRuntimePluginIdScope("voice-call", () => + await gatewayRequestScopeModule.withPluginRuntimePluginIdScope("voice-call", () => runtime.run({ sessionKey: "s-model-only-override", message: "use trusted model-only override", @@ -353,7 +363,7 @@ describe("loadGatewayPlugins", () => { }); test("rejects trusted fallback overrides when the configured allowlist normalizes to empty", async () => { - const serverPlugins = await importServerPluginsModule(); + const serverPlugins = serverPluginsModule; const runtime = await createSubagentRuntime(serverPlugins, { plugins: { entries: { @@ -367,10 +377,8 @@ describe("loadGatewayPlugins", () => { }, }); serverPlugins.setFallbackGatewayContext(createTestContext("fallback-invalid-allowlist")); - const gatewayScopeModule = await import("../plugins/runtime/gateway-request-scope.js"); - await expect( - gatewayScopeModule.withPluginRuntimePluginIdScope("voice-call", () => + gatewayRequestScopeModule.withPluginRuntimePluginIdScope("voice-call", () => runtime.run({ sessionKey: "s-invalid-allowlist", message: "use trusted override", @@ -385,7 +393,7 @@ describe("loadGatewayPlugins", () => { }); test("uses least-privilege synthetic fallback scopes without admin", async () => { - const serverPlugins = await importServerPluginsModule(); + const serverPlugins = serverPluginsModule; const runtime = await createSubagentRuntime(serverPlugins); serverPlugins.setFallbackGatewayContext(createTestContext("synthetic-least-privilege")); @@ -400,14 +408,13 @@ describe("loadGatewayPlugins", () => { }); test("allows fallback session reads with synthetic write scope", async () => { - const serverPlugins = await importServerPluginsModule(); + const serverPlugins = serverPluginsModule; const runtime = await createSubagentRuntime(serverPlugins); serverPlugins.setFallbackGatewayContext(createTestContext("synthetic-session-read")); - const { authorizeOperatorScopesForMethod } = await import("./method-scopes.js"); handleGatewayRequest.mockImplementationOnce(async (opts: HandleGatewayRequestOptions) => { const scopes = Array.isArray(opts.client?.connect?.scopes) ? opts.client.connect.scopes : []; - const auth = authorizeOperatorScopesForMethod("sessions.get", scopes); + const auth = methodScopesModule.authorizeOperatorScopesForMethod("sessions.get", scopes); if (!auth.allowed) { opts.respond(false, undefined, { code: "INVALID_REQUEST", @@ -431,7 +438,7 @@ describe("loadGatewayPlugins", () => { }); test("keeps admin scope for fallback session deletion", async () => { - const serverPlugins = await importServerPluginsModule(); + const serverPlugins = serverPluginsModule; const runtime = await createSubagentRuntime(serverPlugins); serverPlugins.setFallbackGatewayContext(createTestContext("synthetic-delete-session")); @@ -444,7 +451,7 @@ describe("loadGatewayPlugins", () => { }); test("can prefer setup-runtime channel plugins during startup loads", async () => { - const { loadGatewayPlugins } = await importServerPluginsModule(); + const { loadGatewayPlugins } = serverPluginsModule; loadOpenClawPlugins.mockReturnValue(createRegistry([])); const log = { @@ -471,7 +478,7 @@ describe("loadGatewayPlugins", () => { }); test("primes configured bindings during gateway startup", async () => { - const { loadGatewayPlugins } = await importServerPluginsModule(); + const { loadGatewayPlugins } = serverPluginsModule; loadOpenClawPlugins.mockReturnValue(createRegistry([])); const log = { @@ -494,7 +501,7 @@ describe("loadGatewayPlugins", () => { }); test("can suppress duplicate diagnostics when reloading full runtime plugins", async () => { - const { loadGatewayPlugins } = await importServerPluginsModule(); + const { loadGatewayPlugins } = serverPluginsModule; const diagnostics: PluginDiagnostic[] = [ { level: "error", @@ -525,7 +532,7 @@ describe("loadGatewayPlugins", () => { expect(log.info).not.toHaveBeenCalled(); }); test("shares fallback context across module reloads for existing runtimes", async () => { - const first = await importServerPluginsModule(); + const first = serverPluginsModule; const runtime = await createSubagentRuntime(first); const staleContext = createTestContext("stale"); @@ -533,8 +540,7 @@ describe("loadGatewayPlugins", () => { await runtime.run({ sessionKey: "s-1", message: "hello" }); expect(getLastDispatchedContext()).toBe(staleContext); - vi.resetModules(); - const reloaded = await importServerPluginsModule(); + const reloaded = await reloadServerPluginsModule(); const freshContext = createTestContext("fresh"); reloaded.setFallbackGatewayContext(freshContext); @@ -543,7 +549,7 @@ describe("loadGatewayPlugins", () => { }); test("uses updated fallback context after context replacement", async () => { - const serverPlugins = await importServerPluginsModule(); + const serverPlugins = serverPluginsModule; const runtime = await createSubagentRuntime(serverPlugins); const firstContext = createTestContext("before-restart"); const secondContext = createTestContext("after-restart"); @@ -558,7 +564,7 @@ describe("loadGatewayPlugins", () => { }); test("reflects fallback context object mutation at dispatch time", async () => { - const serverPlugins = await importServerPluginsModule(); + const serverPlugins = serverPluginsModule; const runtime = await createSubagentRuntime(serverPlugins); const context = { marker: "before-mutation" } as GatewayRequestContext & { marker: string;