Download src/gateway/server/plugins-http.runtime-scopes.test.ts from SaylorTwift/openclaw: direct link, hf CLI and curl.
- Browser
- Download file 22.7 kB
-
https://huggingface.co/SaylorTwift/openclaw/resolve/main/src/gateway/server/plugins-http.runtime-scopes.test.ts
- Command line
-
hf download hf://SaylorTwift/openclaw/src/gateway/server/plugins-http.runtime-scopes.test.ts
-
curl -L -o plugins-http.runtime-scopes.test.ts https://huggingface.co/SaylorTwift/openclaw/resolve/main/src/gateway/server/plugins-http.runtime-scopes.test.ts
22.7 kB
| /** | |
| * Plugin HTTP runtime-scope integration tests. | |
| */ | |
| import type { IncomingMessage, ServerResponse } from "node:http"; | |
| import { PassThrough } from "node:stream"; | |
| import { afterEach, describe, expect, it, vi } from "vitest"; | |
| import type { SubsystemLogger } from "../../logging/subsystem.js"; | |
| import { createEmptyPluginRegistry } from "../../plugins/registry.js"; | |
| import { setActivePluginRegistry } from "../../plugins/runtime.js"; | |
| import { getPluginRuntimeGatewayRequestScope } from "../../plugins/runtime/gateway-request-scope.js"; | |
| import { createTestApprovalManager } from "../exec-approval-manager.test-support.js"; | |
| import type { AuthorizedGatewayHttpRequest } from "../http-utils.js"; | |
| import { authorizeOperatorScopesForMethod, CLI_DEFAULT_OPERATOR_SCOPES } from "../method-scopes.js"; | |
| import { isApprovalRecordVisibleToClient } from "../server-methods/approval-shared.js"; | |
| import type { GatewayRequestContext } from "../server-methods/types.js"; | |
| import { makeMockHttpResponse } from "../test-http-response.js"; | |
| import { createGatewayTestRegistry } from "./__tests__/test-utils.js"; | |
| import { | |
| createGatewayPluginRequestHandler, | |
| createGatewayPluginUpgradeHandler, | |
| } from "./plugins-http.js"; | |
| const SECURE_HOOK_PATH = "/secure-hook"; | |
| const SECURE_ADMIN_HOOK_PATH = "/secure-admin-hook"; | |
| type PluginHttpRoute = ReturnType<typeof createRoute>; | |
| type PluginRequestHandler = ReturnType<typeof createGatewayPluginRequestHandler>; | |
| type PluginRequestAuthContext = NonNullable<Parameters<PluginRequestHandler>[3]>; | |
| function createRoute(params: { | |
| path: string; | |
| auth: "gateway" | "plugin"; | |
| match?: "exact" | "prefix"; | |
| gatewayRuntimeScopeSurface?: "write-default" | "trusted-operator"; | |
| gatewayMethodDispatchAllowed?: boolean; | |
| handler?: (req: IncomingMessage, res: ServerResponse) => boolean | Promise<boolean>; | |
| }) { | |
| return { | |
| pluginId: "route", | |
| path: params.path, | |
| auth: params.auth, | |
| gatewayRuntimeScopeSurface: params.gatewayRuntimeScopeSurface, | |
| gatewayMethodDispatchAllowed: params.gatewayMethodDispatchAllowed, | |
| match: params.match ?? "exact", | |
| handler: params.handler ?? (() => true), | |
| source: "route", | |
| }; | |
| } | |
| function createMockLogger(): SubsystemLogger { | |
| const child = vi.fn<(name: string) => SubsystemLogger>(); | |
| const logger = { | |
| subsystem: "test/plugins-http-runtime-scopes", | |
| isEnabled: () => true, | |
| trace: vi.fn(), | |
| debug: vi.fn(), | |
| info: vi.fn(), | |
| warn: vi.fn(), | |
| error: vi.fn(), | |
| fatal: vi.fn(), | |
| raw: vi.fn(), | |
| child, | |
| } satisfies SubsystemLogger; | |
| child.mockImplementation(() => logger); | |
| return logger as SubsystemLogger; | |
| } | |
| function assertWriteHelperAllowed() { | |
| const scopes = getPluginRuntimeGatewayRequestScope()?.client?.connect?.scopes ?? []; | |
| const auth = authorizeOperatorScopesForMethod("agent", scopes); | |
| if (!auth.allowed) { | |
| throw new Error(`missing scope: ${auth.missingScope}`); | |
| } | |
| } | |
| function assertAdminHelperAllowed() { | |
| const scopes = getPluginRuntimeGatewayRequestScope()?.client?.connect?.scopes ?? []; | |
| const auth = authorizeOperatorScopesForMethod("set-heartbeats", scopes); | |
| if (!auth.allowed) { | |
| throw new Error(`missing scope: ${auth.missingScope}`); | |
| } | |
| } | |
| function createPluginRequestHandler(params: { | |
| routes: PluginHttpRoute[]; | |
| log?: SubsystemLogger; | |
| getRouteRegistry?: () => ReturnType<typeof createGatewayTestRegistry>; | |
| getGatewayRequestContext?: () => GatewayRequestContext; | |
| }) { | |
| return createGatewayPluginRequestHandler({ | |
| registry: createGatewayTestRegistry({ 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<typeof vi.fn>; | |
| end: ReturnType<typeof vi.fn>; | |
| 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(() => { | |
| setActivePluginRegistry(createEmptyPluginRegistry()); | |
| }); | |
| async function invokeRoute(params: { | |
| path: string; | |
| auth: "gateway" | "plugin"; | |
| gatewayRuntimeScopeSurface?: "write-default" | "trusted-operator"; | |
| gatewayAuthSatisfied: boolean; | |
| gatewayRequestAuth?: AuthorizedGatewayHttpRequest; | |
| gatewayRequestOperatorScopes?: readonly string[]; | |
| }) { | |
| const log = createMockLogger(); | |
| const handler = createPluginRequestHandler({ | |
| routes: [ | |
| createRoute({ | |
| path: params.path, | |
| auth: params.auth, | |
| gatewayRuntimeScopeSurface: params.gatewayRuntimeScopeSurface, | |
| handler: async () => { | |
| assertWriteHelperAllowed(); | |
| return true; | |
| }, | |
| }), | |
| ], | |
| log, | |
| }); | |
| const response = await dispatchPluginRequest(handler, { | |
| path: params.path, | |
| authContext: { | |
| gatewayAuthSatisfied: params.gatewayAuthSatisfied, | |
| gatewayRequestAuth: params.gatewayRequestAuth, | |
| gatewayRequestOperatorScopes: params.gatewayRequestOperatorScopes, | |
| }, | |
| }); | |
| return { log, ...response }; | |
| } | |
| it("keeps plugin-auth routes off write-capable runtime helpers", async () => { | |
| const { handled, res, setHeader, end, log } = await invokeRoute({ | |
| path: "/hook", | |
| auth: "plugin", | |
| gatewayAuthSatisfied: false, | |
| }); | |
| expect(handled).toBe(true); | |
| expectMissingWriteScopeFailure({ res, setHeader, end, log }); | |
| }); | |
| it("preserves write-capable runtime helpers on gateway-auth routes", async () => { | |
| const { handled, res, log } = await invokeRoute({ | |
| path: "/secure-hook", | |
| auth: "gateway", | |
| gatewayAuthSatisfied: true, | |
| gatewayRequestOperatorScopes: ["operator.write"], | |
| }); | |
| expect(handled).toBe(true); | |
| expect(res.statusCode).toBe(200); | |
| expect(log.warn).not.toHaveBeenCalled(); | |
| }); | |
| it("threads plugin route identity and gateway dispatch entitlement into runtime scope", async () => { | |
| let observed: | |
| | { | |
| pluginId: string | undefined; | |
| pluginSource: string | undefined; | |
| gatewayMethodDispatchAllowed: boolean | undefined; | |
| } | |
| | undefined; | |
| 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 { handled, res } = await dispatchPluginRequest(handler, { | |
| path: SECURE_HOOK_PATH, | |
| authContext: { | |
| gatewayAuthSatisfied: true, | |
| gatewayRequestOperatorScopes: ["operator.write"], | |
| }, | |
| }); | |
| expect(handled).toBe(true); | |
| expect(res.statusCode).toBe(200); | |
| expect(observed).toEqual({ | |
| pluginId: "route", | |
| pluginSource: "route", | |
| gatewayMethodDispatchAllowed: true, | |
| }); | |
| }); | |
| it("preserves the verified person on gateway-authenticated plugin runtime clients", async () => { | |
| const authenticatedUserProfile = { | |
| profileId: "profile-guest", | |
| displayName: "Guest", | |
| hasAvatar: false, | |
| updatedAt: 1, | |
| }; | |
| let observedProfile: AuthorizedGatewayHttpRequest["authenticatedUserProfile"]; | |
| const handler = createPluginRequestHandler({ | |
| routes: [ | |
| createRoute({ | |
| path: SECURE_HOOK_PATH, | |
| auth: "gateway", | |
| handler: async () => { | |
| observedProfile = | |
| getPluginRuntimeGatewayRequestScope()?.client?.authenticatedUserProfile; | |
| return true; | |
| }, | |
| }), | |
| ], | |
| }); | |
| const { handled } = await dispatchPluginRequest(handler, { | |
| path: SECURE_HOOK_PATH, | |
| authContext: { | |
| gatewayAuthSatisfied: true, | |
| gatewayRequestAuth: { | |
| authMethod: "trusted-proxy", | |
| trustDeclaredOperatorScopes: true, | |
| authenticatedUserProfile, | |
| }, | |
| gatewayRequestOperatorScopes: ["operator.read"], | |
| }, | |
| }); | |
| expect(handled).toBe(true); | |
| expect(observedProfile).toEqual(authenticatedUserProfile); | |
| }); | |
| it.each([ | |
| { auth: "gateway" as const, authMethod: "token" as const, systemActor: true }, | |
| { auth: "gateway" as const, authMethod: "password" as const, systemActor: true }, | |
| { auth: "gateway" as const, authMethod: "trusted-proxy" as const, systemActor: false }, | |
| { auth: "plugin" as const, authMethod: "token" as const, systemActor: false }, | |
| ])( | |
| "preserves system authority only for authenticated shared-secret gateway routes ($auth/$authMethod)", | |
| async ({ auth, authMethod, systemActor }) => { | |
| const authenticatedUserProfile = { | |
| profileId: "profile-owner", | |
| displayName: "Owner", | |
| hasAvatar: false, | |
| updatedAt: 1, | |
| }; | |
| let observedActor: unknown; | |
| let observedProfile: AuthorizedGatewayHttpRequest["authenticatedUserProfile"]; | |
| const handler = createPluginRequestHandler({ | |
| routes: [ | |
| createRoute({ | |
| path: SECURE_HOOK_PATH, | |
| auth, | |
| handler: async () => { | |
| observedActor = | |
| getPluginRuntimeGatewayRequestScope()?.client?.internal?.operatorRoleActor; | |
| observedProfile = | |
| getPluginRuntimeGatewayRequestScope()?.client?.authenticatedUserProfile; | |
| return true; | |
| }, | |
| }), | |
| ], | |
| }); | |
| const { handled } = await dispatchPluginRequest(handler, { | |
| path: SECURE_HOOK_PATH, | |
| authContext: { | |
| gatewayAuthSatisfied: true, | |
| gatewayRequestAuth: { | |
| authMethod, | |
| trustDeclaredOperatorScopes: false, | |
| authenticatedUserProfile, | |
| ...(authMethod === "token" || authMethod === "password" | |
| ? { operatorRoleActor: { kind: "system" as const } } | |
| : {}), | |
| }, | |
| gatewayRequestOperatorScopes: ["operator.write"], | |
| }, | |
| }); | |
| expect(handled).toBe(true); | |
| expect(observedActor).toEqual(systemActor ? { kind: "system" } : undefined); | |
| expect(observedProfile).toEqual(auth === "gateway" ? authenticatedUserProfile : undefined); | |
| }, | |
| ); | |
| it("uses server-local routes and gateway context when the active registry belongs to another gateway", async () => { | |
| const serverAContext = { label: "server-a" } as unknown as GatewayRequestContext; | |
| const serverBContext = { label: "server-b" } as unknown as GatewayRequestContext; | |
| const observed: Array<{ route: string; context?: GatewayRequestContext }> = []; | |
| const serverARegistry = createGatewayTestRegistry({ | |
| httpRoutes: [ | |
| createRoute({ | |
| path: SECURE_HOOK_PATH, | |
| auth: "gateway", | |
| handler: async () => { | |
| const context = getPluginRuntimeGatewayRequestScope()?.context; | |
| observed.push({ route: "server-a", ...(context ? { context } : {}) }); | |
| return true; | |
| }, | |
| }), | |
| ], | |
| }); | |
| const serverBRegistry = createGatewayTestRegistry({ | |
| httpRoutes: [ | |
| createRoute({ | |
| path: SECURE_HOOK_PATH, | |
| auth: "gateway", | |
| handler: async () => { | |
| const context = getPluginRuntimeGatewayRequestScope()?.context; | |
| observed.push({ route: "server-b", ...(context ? { context } : {}) }); | |
| return true; | |
| }, | |
| }), | |
| ], | |
| }); | |
| setActivePluginRegistry(serverBRegistry); | |
| const handlerA = createGatewayPluginRequestHandler({ | |
| registry: serverARegistry, | |
| getRouteRegistry: () => serverARegistry, | |
| log: createMockLogger(), | |
| getGatewayRequestContext: () => serverAContext, | |
| }); | |
| const handlerB = createGatewayPluginRequestHandler({ | |
| registry: serverBRegistry, | |
| getRouteRegistry: () => serverBRegistry, | |
| log: createMockLogger(), | |
| getGatewayRequestContext: () => serverBContext, | |
| }); | |
| const responseA = makeMockHttpResponse(); | |
| const handledA = await handlerA( | |
| { url: SECURE_HOOK_PATH } as IncomingMessage, | |
| responseA.res, | |
| undefined, | |
| { | |
| gatewayAuthSatisfied: true, | |
| gatewayRequestOperatorScopes: ["operator.write"], | |
| }, | |
| ); | |
| const responseB = makeMockHttpResponse(); | |
| const handledB = await handlerB( | |
| { url: SECURE_HOOK_PATH } as IncomingMessage, | |
| responseB.res, | |
| undefined, | |
| { | |
| gatewayAuthSatisfied: true, | |
| gatewayRequestOperatorScopes: ["operator.write"], | |
| }, | |
| ); | |
| expect(handledA).toBe(true); | |
| expect(handledB).toBe(true); | |
| expect(responseA.res.statusCode).toBe(200); | |
| expect(responseB.res.statusCode).toBe(200); | |
| expect(observed).toEqual([ | |
| { route: "server-a", context: serverAContext }, | |
| { route: "server-b", context: serverBContext }, | |
| ]); | |
| }); | |
| it.each(["HTTP", "WebSocket"] as const)( | |
| "binds reloaded %s handlers to the registry that owns their route", | |
| async (transport) => { | |
| const observed: Array<ReturnType<typeof createGatewayTestRegistry> | undefined> = []; | |
| const observeScope = () => { | |
| observed.push(getPluginRuntimeGatewayRequestScope()?.pluginRegistry); | |
| return true; | |
| }; | |
| const createRegistry = () => | |
| createGatewayTestRegistry({ | |
| httpRoutes: [ | |
| { | |
| ...createRoute({ path: SECURE_HOOK_PATH, auth: "gateway", handler: observeScope }), | |
| handleUpgrade: observeScope, | |
| }, | |
| ], | |
| }); | |
| const startupRegistry = createRegistry(); | |
| let currentRegistry = startupRegistry; | |
| const options = { | |
| registry: startupRegistry, | |
| getRouteRegistry: () => currentRegistry, | |
| log: createMockLogger(), | |
| }; | |
| const requestHandler = createGatewayPluginRequestHandler(options); | |
| const upgradeHandler = createGatewayPluginUpgradeHandler(options); | |
| const socket = new PassThrough(); | |
| try { | |
| const dispatch = async () => | |
| transport === "HTTP" | |
| ? (await dispatchTrustedGatewayRequest(requestHandler, SECURE_HOOK_PATH)).handled | |
| : await upgradeHandler( | |
| { url: SECURE_HOOK_PATH } as IncomingMessage, | |
| socket, | |
| Buffer.alloc(0), | |
| undefined, | |
| { gatewayAuthSatisfied: true, gatewayRequestOperatorScopes: ["operator.write"] }, | |
| ); | |
| expect(await dispatch()).toBe(true); | |
| currentRegistry = createRegistry(); | |
| expect(await dispatch()).toBe(true); | |
| expect(observed).toHaveLength(2); | |
| expect(observed[0]).toBe(startupRegistry); | |
| expect(observed[1]).toBe(currentRegistry); | |
| } finally { | |
| socket.destroy(); | |
| } | |
| }, | |
| ); | |
| it("does not give approval-scoped gateway-auth routes global approval visibility", async (testContext) => { | |
| const manager = createTestApprovalManager<{ command: string }>(testContext); | |
| const record = manager.create({ command: "echo ok" }, 60_000, "route-hidden-approval"); | |
| record.requestedByDeviceId = "device-owner"; | |
| record.requestedByConnId = "conn-owner"; | |
| record.requestedByClientId = "client-owner"; | |
| let observedApprovalRuntime: boolean | undefined; | |
| let observedVisibility: boolean | undefined; | |
| 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 { handled, res } = await dispatchPluginRequest(handler, { | |
| path: SECURE_HOOK_PATH, | |
| authContext: { | |
| gatewayAuthSatisfied: true, | |
| gatewayRequestOperatorScopes: ["operator.approvals"], | |
| }, | |
| }); | |
| expect(handled).toBe(true); | |
| expect(res.statusCode).toBe(200); | |
| expect(observedApprovalRuntime).not.toBe(true); | |
| expect(observedVisibility).toBe(false); | |
| }); | |
| it("fails closed when gateway-auth route runtime scopes are missing", async () => { | |
| const { handled, res, log } = await invokeRoute({ | |
| path: "/secure-hook", | |
| auth: "gateway", | |
| gatewayAuthSatisfied: true, | |
| }); | |
| expect(handled).toBe(false); | |
| expect(res.statusCode).toBe(200); | |
| expect(log.warn).toHaveBeenCalledWith( | |
| "plugin http route blocked without caller scope context (/secure-hook)", | |
| ); | |
| }); | |
| it("does not allow write helpers for read-scoped gateway-auth requests", async () => { | |
| const { handled, res, setHeader, end, log } = await invokeRoute({ | |
| path: "/secure-hook", | |
| auth: "gateway", | |
| gatewayAuthSatisfied: true, | |
| gatewayRequestOperatorScopes: ["operator.read"], | |
| }); | |
| expect(handled).toBe(true); | |
| 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 = 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 = await dispatchTrustedGatewayRequest(handler, SECURE_ADMIN_HOOK_PATH); | |
| expect(response.handled).toBe(true); | |
| expect(response.res.statusCode).toBe(200); | |
| expect(log.warn).not.toHaveBeenCalled(); | |
| expect(observedScopes).toEqual(CLI_DEFAULT_OPERATOR_SCOPES); | |
| }); | |
| it("scopes runtime privileges per matched route for exact/prefix overlap", async () => { | |
| const observed: Array<{ route: "exact" | "prefix"; scopes: string[] }> = []; | |
| const log = createMockLogger(); | |
| const handler = createGatewayPluginRequestHandler({ | |
| registry: createGatewayTestRegistry({ | |
| httpRoutes: [ | |
| createRoute({ | |
| path: "/secure/admin-hook", | |
| auth: "gateway", | |
| match: "exact", | |
| handler: async () => { | |
| observed.push({ | |
| route: "exact", | |
| scopes: | |
| getPluginRuntimeGatewayRequestScope()?.client?.connect?.scopes?.slice() ?? [], | |
| }); | |
| return false; | |
| }, | |
| }), | |
| createRoute({ | |
| path: "/secure", | |
| auth: "gateway", | |
| match: "prefix", | |
| gatewayRuntimeScopeSurface: "trusted-operator", | |
| handler: async () => { | |
| observed.push({ | |
| route: "prefix", | |
| scopes: | |
| getPluginRuntimeGatewayRequestScope()?.client?.connect?.scopes?.slice() ?? [], | |
| }); | |
| assertAdminHelperAllowed(); | |
| return true; | |
| }, | |
| }), | |
| ], | |
| }), | |
| log, | |
| }); | |
| const response = await dispatchTrustedGatewayRequest(handler, "/secure/admin-hook"); | |
| expect(response.handled).toBe(true); | |
| expect(response.res.statusCode).toBe(200); | |
| expect(log.warn).not.toHaveBeenCalled(); | |
| expect(observed).toHaveLength(2); | |
| expect(observed[0]).toEqual({ | |
| route: "exact", | |
| scopes: ["operator.write"], | |
| }); | |
| expect(observed[1]?.route).toBe("prefix"); | |
| expect(observed[1]?.scopes).toEqual(CLI_DEFAULT_OPERATOR_SCOPES); | |
| }); | |
| it.each([ | |
| { | |
| auth: "plugin" as const, | |
| gatewayAuthSatisfied: false, | |
| path: "/hook", | |
| gatewayRequestOperatorScopes: undefined, | |
| expectedScopes: [], | |
| }, | |
| { | |
| auth: "gateway" as const, | |
| gatewayAuthSatisfied: true, | |
| path: "/secure-hook", | |
| gatewayRequestOperatorScopes: ["operator.read"], | |
| expectedScopes: ["operator.read"], | |
| }, | |
| ])( | |
| "maps $auth routes to $expectedScopes", | |
| async ({ auth, gatewayAuthSatisfied, gatewayRequestOperatorScopes, path, expectedScopes }) => { | |
| let observedScopes: string[] | undefined; | |
| const handler = createGatewayPluginRequestHandler({ | |
| registry: createGatewayTestRegistry({ | |
| httpRoutes: [ | |
| createRoute({ | |
| path, | |
| auth, | |
| handler: vi.fn(async () => { | |
| observedScopes = | |
| getPluginRuntimeGatewayRequestScope()?.client?.connect?.scopes?.slice() ?? []; | |
| return true; | |
| }), | |
| }), | |
| ], | |
| }), | |
| log: createMockLogger(), | |
| }); | |
| const { res } = makeMockHttpResponse(); | |
| const handled = await handler({ url: path } as IncomingMessage, res, undefined, { | |
| gatewayAuthSatisfied, | |
| gatewayRequestOperatorScopes, | |
| }); | |
| expect(handled).toBe(true); | |
| expect(res.statusCode).toBe(200); | |
| expect(observedScopes).toEqual(expectedScopes); | |
| }, | |
| ); | |
| }); | |