From e85be626a4aa089a03d4579b1b5affe5fbbd52ff Mon Sep 17 00:00:00 2001 From: Vincent Koc Date: Mon, 1 Jun 2026 11:07:29 +0200 Subject: [PATCH] refactor: share plugin runtime scope test setup --- .../plugins-http.runtime-scopes.test.ts | 276 ++++++++++-------- 1 file changed, 151 insertions(+), 125 deletions(-) diff --git a/src/gateway/server/plugins-http.runtime-scopes.test.ts b/src/gateway/server/plugins-http.runtime-scopes.test.ts index 6f772a51cfc..5921792de43 100644 --- a/src/gateway/server/plugins-http.runtime-scopes.test.ts +++ b/src/gateway/server/plugins-http.runtime-scopes.test.ts @@ -17,6 +17,13 @@ import { makeMockHttpResponse } from "../test-http-response.js"; import { createTestRegistry } from "./__tests__/test-utils.js"; import { createGatewayPluginRequestHandler } from "./plugins-http.js"; +const SECURE_HOOK_PATH = "/secure-hook"; +const SECURE_ADMIN_HOOK_PATH = "/secure-admin-hook"; + +type PluginHttpRoute = ReturnType; +type PluginRequestHandler = ReturnType; +type PluginRequestAuthContext = NonNullable[3]>; + function createRoute(params: { path: string; auth: "gateway" | "plugin"; @@ -71,6 +78,64 @@ function assertAdminHelperAllowed() { } } +function createPluginRequestHandler(params: { + routes: PluginHttpRoute[]; + log?: SubsystemLogger; + getRouteRegistry?: () => ReturnType; + getGatewayRequestContext?: () => GatewayRequestContext; +}) { + return createGatewayPluginRequestHandler({ + registry: createTestRegistry({ httpRoutes: params.routes }), + ...(params.getRouteRegistry ? { getRouteRegistry: params.getRouteRegistry } : {}), + log: params.log ?? createMockLogger(), + ...(params.getGatewayRequestContext + ? { getGatewayRequestContext: params.getGatewayRequestContext } + : {}), + }); +} + +async function dispatchPluginRequest( + handler: PluginRequestHandler, + params: { + path: string; + authContext: PluginRequestAuthContext; + }, +) { + const response = makeMockHttpResponse(); + const handled = await handler( + { url: params.path } as IncomingMessage, + response.res, + undefined, + params.authContext, + ); + return { handled, ...response }; +} + +async function dispatchTrustedGatewayRequest(handler: PluginRequestHandler, path: string) { + return await dispatchPluginRequest(handler, { + path, + authContext: { + gatewayAuthSatisfied: true, + gatewayRequestAuth: { authMethod: "token", trustDeclaredOperatorScopes: false }, + gatewayRequestOperatorScopes: ["operator.write"], + }, + }); +} + +function expectMissingWriteScopeFailure(params: { + res: ServerResponse; + setHeader: ReturnType; + end: ReturnType; + log: SubsystemLogger; +}) { + expect(params.res.statusCode).toBe(500); + expect(params.setHeader).toHaveBeenCalledWith("Content-Type", "text/plain; charset=utf-8"); + expect(params.end).toHaveBeenCalledWith("Internal Server Error"); + expect(params.log.warn).toHaveBeenCalledWith( + "plugin http route failed (route): Error: missing scope: operator.write", + ); +} + describe("plugin HTTP route runtime scopes", () => { afterEach(() => { releasePinnedPluginHttpRouteRegistry(); @@ -86,35 +151,30 @@ describe("plugin HTTP route runtime scopes", () => { gatewayRequestOperatorScopes?: readonly string[]; }) { const log = createMockLogger(); - const handler = createGatewayPluginRequestHandler({ - registry: createTestRegistry({ - httpRoutes: [ - createRoute({ - path: params.path, - auth: params.auth, - gatewayRuntimeScopeSurface: params.gatewayRuntimeScopeSurface, - handler: async () => { - assertWriteHelperAllowed(); - return true; - }, - }), - ], - }), + const handler = createPluginRequestHandler({ + routes: [ + createRoute({ + path: params.path, + auth: params.auth, + gatewayRuntimeScopeSurface: params.gatewayRuntimeScopeSurface, + handler: async () => { + assertWriteHelperAllowed(); + return true; + }, + }), + ], log, }); - const response = makeMockHttpResponse(); - const handled = await handler( - { url: params.path } as IncomingMessage, - response.res, - undefined, - { + const response = await dispatchPluginRequest(handler, { + path: params.path, + authContext: { gatewayAuthSatisfied: params.gatewayAuthSatisfied, gatewayRequestAuth: params.gatewayRequestAuth, gatewayRequestOperatorScopes: params.gatewayRequestOperatorScopes, }, - ); - return { handled, log, ...response }; + }); + return { log, ...response }; } it("keeps plugin-auth routes off write-capable runtime helpers", async () => { @@ -125,12 +185,7 @@ describe("plugin HTTP route runtime scopes", () => { }); expect(handled).toBe(true); - expect(res.statusCode).toBe(500); - expect(setHeader).toHaveBeenCalledWith("Content-Type", "text/plain; charset=utf-8"); - expect(end).toHaveBeenCalledWith("Internal Server Error"); - expect(log.warn).toHaveBeenCalledWith( - "plugin http route failed (route): Error: missing scope: operator.write", - ); + expectMissingWriteScopeFailure({ res, setHeader, end, log }); }); it("preserves write-capable runtime helpers on gateway-auth routes", async () => { @@ -154,32 +209,31 @@ describe("plugin HTTP route runtime scopes", () => { gatewayMethodDispatchAllowed: boolean | undefined; } | undefined; - const handler = createGatewayPluginRequestHandler({ - registry: createTestRegistry({ - httpRoutes: [ - createRoute({ - path: "/secure-hook", - auth: "gateway", - gatewayMethodDispatchAllowed: true, - handler: async () => { - const scope = getPluginRuntimeGatewayRequestScope(); - observed = { - pluginId: scope?.pluginId, - pluginSource: scope?.pluginSource, - gatewayMethodDispatchAllowed: scope?.gatewayMethodDispatchAllowed, - }; - return true; - }, - }), - ], - }), - log: createMockLogger(), + const handler = createPluginRequestHandler({ + routes: [ + createRoute({ + path: SECURE_HOOK_PATH, + auth: "gateway", + gatewayMethodDispatchAllowed: true, + handler: async () => { + const scope = getPluginRuntimeGatewayRequestScope(); + observed = { + pluginId: scope?.pluginId, + pluginSource: scope?.pluginSource, + gatewayMethodDispatchAllowed: scope?.gatewayMethodDispatchAllowed, + }; + return true; + }, + }), + ], }); - const { res } = makeMockHttpResponse(); - const handled = await handler({ url: "/secure-hook" } as IncomingMessage, res, undefined, { - gatewayAuthSatisfied: true, - gatewayRequestOperatorScopes: ["operator.write"], + const { handled, res } = await dispatchPluginRequest(handler, { + path: SECURE_HOOK_PATH, + authContext: { + gatewayAuthSatisfied: true, + gatewayRequestOperatorScopes: ["operator.write"], + }, }); expect(handled).toBe(true); @@ -198,7 +252,7 @@ describe("plugin HTTP route runtime scopes", () => { const serverARegistry = createTestRegistry({ httpRoutes: [ createRoute({ - path: "/secure-hook", + path: SECURE_HOOK_PATH, auth: "gateway", handler: async () => { const context = getPluginRuntimeGatewayRequestScope()?.context; @@ -211,7 +265,7 @@ describe("plugin HTTP route runtime scopes", () => { const serverBRegistry = createTestRegistry({ httpRoutes: [ createRoute({ - path: "/secure-hook", + path: SECURE_HOOK_PATH, auth: "gateway", handler: async () => { const context = getPluginRuntimeGatewayRequestScope()?.context; @@ -240,7 +294,7 @@ describe("plugin HTTP route runtime scopes", () => { const responseA = makeMockHttpResponse(); const handledA = await handlerA( - { url: "/secure-hook" } as IncomingMessage, + { url: SECURE_HOOK_PATH } as IncomingMessage, responseA.res, undefined, { @@ -250,7 +304,7 @@ describe("plugin HTTP route runtime scopes", () => { ); const responseB = makeMockHttpResponse(); const handledB = await handlerB( - { url: "/secure-hook" } as IncomingMessage, + { url: SECURE_HOOK_PATH } as IncomingMessage, responseB.res, undefined, { @@ -277,31 +331,30 @@ describe("plugin HTTP route runtime scopes", () => { record.requestedByClientId = "client-owner"; let observedApprovalRuntime: boolean | undefined; let observedVisibility: boolean | undefined; - const handler = createGatewayPluginRequestHandler({ - registry: createTestRegistry({ - httpRoutes: [ - createRoute({ - path: "/secure-hook", - auth: "gateway", - handler: async () => { - const runtimeClient = getPluginRuntimeGatewayRequestScope()?.client; - observedApprovalRuntime = runtimeClient?.internal?.approvalRuntime; - observedVisibility = isApprovalRecordVisibleToClient({ - record, - client: runtimeClient ?? null, - }); - return true; - }, - }), - ], - }), - log: createMockLogger(), + const handler = createPluginRequestHandler({ + routes: [ + createRoute({ + path: SECURE_HOOK_PATH, + auth: "gateway", + handler: async () => { + const runtimeClient = getPluginRuntimeGatewayRequestScope()?.client; + observedApprovalRuntime = runtimeClient?.internal?.approvalRuntime; + observedVisibility = isApprovalRecordVisibleToClient({ + record, + client: runtimeClient ?? null, + }); + return true; + }, + }), + ], }); - const { res } = makeMockHttpResponse(); - const handled = await handler({ url: "/secure-hook" } as IncomingMessage, res, undefined, { - gatewayAuthSatisfied: true, - gatewayRequestOperatorScopes: ["operator.approvals"], + const { handled, res } = await dispatchPluginRequest(handler, { + path: SECURE_HOOK_PATH, + authContext: { + gatewayAuthSatisfied: true, + gatewayRequestOperatorScopes: ["operator.approvals"], + }, }); expect(handled).toBe(true); @@ -333,49 +386,32 @@ describe("plugin HTTP route runtime scopes", () => { }); expect(handled).toBe(true); - expect(res.statusCode).toBe(500); - expect(setHeader).toHaveBeenCalledWith("Content-Type", "text/plain; charset=utf-8"); - expect(end).toHaveBeenCalledWith("Internal Server Error"); - expect(log.warn).toHaveBeenCalledWith( - "plugin http route failed (route): Error: missing scope: operator.write", - ); + expectMissingWriteScopeFailure({ res, setHeader, end, log }); }); it("restores trusted-operator defaults for routes opting into trusted surface", async () => { let observedScopes: string[] | undefined; const log = createMockLogger(); - const handler = createGatewayPluginRequestHandler({ - registry: createTestRegistry({ - httpRoutes: [ - createRoute({ - path: "/secure-admin-hook", - auth: "gateway", - gatewayRuntimeScopeSurface: "trusted-operator", - handler: async () => { - observedScopes = - getPluginRuntimeGatewayRequestScope()?.client?.connect?.scopes?.slice() ?? []; - assertAdminHelperAllowed(); - return true; - }, - }), - ], - }), + const handler = createPluginRequestHandler({ + routes: [ + createRoute({ + path: SECURE_ADMIN_HOOK_PATH, + auth: "gateway", + gatewayRuntimeScopeSurface: "trusted-operator", + handler: async () => { + observedScopes = + getPluginRuntimeGatewayRequestScope()?.client?.connect?.scopes?.slice() ?? []; + assertAdminHelperAllowed(); + return true; + }, + }), + ], log, }); - const response = makeMockHttpResponse(); - const handled = await handler( - { url: "/secure-admin-hook" } as IncomingMessage, - response.res, - undefined, - { - gatewayAuthSatisfied: true, - gatewayRequestAuth: { authMethod: "token", trustDeclaredOperatorScopes: false }, - gatewayRequestOperatorScopes: ["operator.write"], - }, - ); + const response = await dispatchTrustedGatewayRequest(handler, SECURE_ADMIN_HOOK_PATH); - expect(handled).toBe(true); + expect(response.handled).toBe(true); expect(response.res.statusCode).toBe(200); expect(log.warn).not.toHaveBeenCalled(); expect(observedScopes).toEqual(CLI_DEFAULT_OPERATOR_SCOPES); @@ -420,19 +456,9 @@ describe("plugin HTTP route runtime scopes", () => { log, }); - const response = makeMockHttpResponse(); - const handled = await handler( - { url: "/secure/admin-hook" } as IncomingMessage, - response.res, - undefined, - { - gatewayAuthSatisfied: true, - gatewayRequestAuth: { authMethod: "token", trustDeclaredOperatorScopes: false }, - gatewayRequestOperatorScopes: ["operator.write"], - }, - ); + const response = await dispatchTrustedGatewayRequest(handler, "/secure/admin-hook"); - expect(handled).toBe(true); + expect(response.handled).toBe(true); expect(response.res.statusCode).toBe(200); expect(log.warn).not.toHaveBeenCalled(); expect(observed).toHaveLength(2);