Download src/gateway/server/plugins-http.test.ts from SaylorTwift/openclaw: direct link, hf CLI and curl.
- Browser
- Download file 23 kB
-
https://huggingface.co/SaylorTwift/openclaw/resolve/main/src/gateway/server/plugins-http.test.ts
- Command line
-
hf download hf://SaylorTwift/openclaw/src/gateway/server/plugins-http.test.ts
-
curl -L -o plugins-http.test.ts https://huggingface.co/SaylorTwift/openclaw/resolve/main/src/gateway/server/plugins-http.test.ts
23 kB
| // Plugin HTTP routing tests cover route matching, gateway auth decisions, and upgrade dispatch. | |
| import { createServer, type IncomingMessage, type ServerResponse } from "node:http"; | |
| import type { Duplex } from "node:stream"; | |
| import { afterEach, describe, expect, it, vi } from "vitest"; | |
| import { PluginInstance } from "../../plugins/plugin-instance.js"; | |
| import { createEmptyPluginRegistry } from "../../plugins/registry.js"; | |
| import { setActivePluginRegistry } from "../../plugins/runtime.js"; | |
| import { getPluginRuntimeGatewayRequestScope } from "../../plugins/runtime/gateway-request-scope.js"; | |
| import { makeMockHttpResponse } from "../test-http-response.js"; | |
| import { createGatewayTestRegistry } from "./__tests__/test-utils.js"; | |
| import { | |
| createGatewayPluginUpgradeHandler, | |
| createGatewayPluginRequestHandler, | |
| isPluginAuthenticatedRoutePath, | |
| isRegisteredPluginHttpRoutePath, | |
| shouldEnforceGatewayAuthForPluginPath, | |
| } from "./plugins-http.js"; | |
| type PluginHandlerLog = Parameters<typeof createGatewayPluginRequestHandler>[0]["log"]; | |
| const CANVAS_WS_PATH = "/__openclaw__/canvas/ws"; | |
| function createPluginLog(): PluginHandlerLog { | |
| return { warn: vi.fn() } as unknown as PluginHandlerLog; | |
| } | |
| function createRoute(params: { | |
| path: string; | |
| pluginId?: string; | |
| auth?: "gateway" | "plugin"; | |
| match?: "exact" | "prefix"; | |
| handler?: (req: IncomingMessage, res: ServerResponse) => boolean | void | Promise<boolean | void>; | |
| handleUpgrade?: ( | |
| req: IncomingMessage, | |
| socket: Duplex, | |
| head: Buffer, | |
| ) => boolean | void | Promise<boolean | void>; | |
| }) { | |
| return { | |
| pluginId: params.pluginId ?? "route", | |
| path: params.path, | |
| auth: params.auth ?? "plugin", | |
| match: params.match ?? "exact", | |
| handler: params.handler ?? (() => {}), | |
| handleUpgrade: params.handleUpgrade, | |
| source: params.pluginId ?? "route", | |
| }; | |
| } | |
| function createMockUpgradeSocket() { | |
| const socket = { | |
| chunks: [] as string[], | |
| destroyed: false, | |
| write(chunk: string) { | |
| socket.chunks.push(chunk); | |
| }, | |
| end(chunk: string, callback: () => void) { | |
| socket.write(chunk); | |
| callback(); | |
| }, | |
| destroy() { | |
| socket.destroyed = true; | |
| }, | |
| } as unknown as Duplex & { chunks: string[]; destroyed: boolean }; | |
| return socket; | |
| } | |
| function buildRepeatedEncodedSlash(depth: number): string { | |
| let encodedSlash = "%2f"; | |
| for (let i = 1; i < depth; i++) { | |
| encodedSlash = encodedSlash.replace(/%/g, "%25"); | |
| } | |
| return encodedSlash; | |
| } | |
| function createSecurePluginRouteHandler(params: { | |
| exactPluginHandler: () => boolean | Promise<boolean>; | |
| prefixGatewayHandler: () => boolean | Promise<boolean>; | |
| }) { | |
| return createGatewayPluginRequestHandler({ | |
| registry: createGatewayTestRegistry({ | |
| httpRoutes: [ | |
| createRoute({ | |
| path: "/plugin/secure/report", | |
| match: "exact", | |
| auth: "plugin", | |
| handler: params.exactPluginHandler, | |
| }), | |
| createRoute({ | |
| path: "/plugin/secure", | |
| match: "prefix", | |
| auth: "gateway", | |
| handler: params.prefixGatewayHandler, | |
| }), | |
| ], | |
| }), | |
| log: createPluginLog(), | |
| }); | |
| } | |
| async function invokeSecureGatewayRoute(params: { | |
| gatewayAuthSatisfied: boolean; | |
| gatewayRequestOperatorScopes?: readonly string[]; | |
| }) { | |
| const exactPluginHandler = vi.fn(async () => false); | |
| const prefixGatewayHandler = vi.fn(async () => true); | |
| const handler = createSecurePluginRouteHandler({ | |
| exactPluginHandler, | |
| prefixGatewayHandler, | |
| }); | |
| const { res } = makeMockHttpResponse(); | |
| const handled = await handler( | |
| { url: "/plugin/secure/report" } as IncomingMessage, | |
| res, | |
| undefined, | |
| { | |
| gatewayAuthSatisfied: params.gatewayAuthSatisfied, | |
| gatewayRequestOperatorScopes: params.gatewayRequestOperatorScopes, | |
| }, | |
| ); | |
| return { handled, exactPluginHandler, prefixGatewayHandler }; | |
| } | |
| async function invokeRouteAndCollectRuntimeScopes(params: { | |
| path: string; | |
| auth: "gateway" | "plugin"; | |
| gatewayAuthSatisfied: boolean; | |
| gatewayRequestOperatorScopes?: readonly string[]; | |
| }) { | |
| let observedScopes: string[] | undefined; | |
| const handler = createGatewayPluginRequestHandler({ | |
| registry: createGatewayTestRegistry({ | |
| httpRoutes: [ | |
| createRoute({ | |
| path: params.path, | |
| auth: params.auth, | |
| handler: async () => { | |
| observedScopes = | |
| getPluginRuntimeGatewayRequestScope()?.client?.connect?.scopes?.slice() ?? []; | |
| return true; | |
| }, | |
| }), | |
| ], | |
| }), | |
| log: createPluginLog(), | |
| }); | |
| const response = makeMockHttpResponse(); | |
| const handled = await handler({ url: params.path } as IncomingMessage, response.res, undefined, { | |
| gatewayAuthSatisfied: params.gatewayAuthSatisfied, | |
| gatewayRequestOperatorScopes: params.gatewayRequestOperatorScopes, | |
| }); | |
| return { handled, observedScopes, ...response }; | |
| } | |
| async function invokeCanvasGatewayUpgrade(params: { gatewayAuthSatisfied: boolean }) { | |
| const routeUpgradeHandler = vi.fn(async () => true); | |
| const handler = createGatewayPluginUpgradeHandler({ | |
| registry: createGatewayTestRegistry({ | |
| httpRoutes: [ | |
| createRoute({ | |
| path: CANVAS_WS_PATH, | |
| auth: "gateway", | |
| handleUpgrade: routeUpgradeHandler, | |
| }), | |
| ], | |
| }), | |
| log: createPluginLog(), | |
| }); | |
| const socket = createMockUpgradeSocket(); | |
| const handled = await handler( | |
| { url: CANVAS_WS_PATH } as IncomingMessage, | |
| socket, | |
| Buffer.alloc(0), | |
| undefined, | |
| { | |
| gatewayAuthSatisfied: params.gatewayAuthSatisfied, | |
| ...(params.gatewayAuthSatisfied ? { gatewayRequestOperatorScopes: ["operator.read"] } : {}), | |
| }, | |
| ); | |
| return { handled, routeUpgradeHandler, socket }; | |
| } | |
| describe("createGatewayPluginRequestHandler", () => { | |
| afterEach(() => { | |
| setActivePluginRegistry(createEmptyPluginRegistry()); | |
| }); | |
| it("fences an identity-preserved route after its plugin instance retires", async () => { | |
| const instance = new PluginInstance("identity-route"); | |
| const routeHandler = instance.adopt(vi.fn(async () => true)); | |
| const log = createPluginLog(); | |
| const handler = createGatewayPluginRequestHandler({ | |
| registry: createGatewayTestRegistry({ | |
| httpRoutes: [createRoute({ path: "/identity", handler: routeHandler })], | |
| }), | |
| log, | |
| }); | |
| await instance.dispose(); | |
| const { res } = makeMockHttpResponse(); | |
| await expect(handler({ url: "/identity" } as IncomingMessage, res)).resolves.toBe(true); | |
| expect(routeHandler).not.toHaveBeenCalled(); | |
| expect(log.warn).toHaveBeenCalledWith( | |
| expect.stringContaining("Plugin identity-route was reloaded or disabled"), | |
| ); | |
| }); | |
| it("keeps unauthenticated plugin routes off operator runtime scopes", async () => { | |
| const { handled, observedScopes, res } = await invokeRouteAndCollectRuntimeScopes({ | |
| path: "/hook", | |
| auth: "plugin", | |
| gatewayAuthSatisfied: false, | |
| }); | |
| expect(handled).toBe(true); | |
| expect(res.statusCode).toBe(200); | |
| expect(observedScopes).toStrictEqual([]); | |
| }); | |
| it("preserves gateway-authenticated plugin route runtime scopes from request auth", async () => { | |
| const { handled, observedScopes, res } = await invokeRouteAndCollectRuntimeScopes({ | |
| path: "/secure-hook", | |
| auth: "gateway", | |
| gatewayAuthSatisfied: true, | |
| gatewayRequestOperatorScopes: ["operator.read"], | |
| }); | |
| expect(handled).toBe(true); | |
| expect(res.statusCode).toBe(200); | |
| expect(observedScopes).toEqual(["operator.read"]); | |
| }); | |
| it("returns false when no routes are registered", async () => { | |
| const log = createPluginLog(); | |
| const handler = createGatewayPluginRequestHandler({ | |
| registry: createGatewayTestRegistry(), | |
| log, | |
| }); | |
| const { res } = makeMockHttpResponse(); | |
| const handled = await handler({} as IncomingMessage, res); | |
| expect(handled).toBe(false); | |
| }); | |
| it("handles exact route matches", async () => { | |
| const routeHandler = vi.fn(async (_req, res: ServerResponse) => { | |
| res.statusCode = 200; | |
| }); | |
| const handler = createGatewayPluginRequestHandler({ | |
| registry: createGatewayTestRegistry({ | |
| httpRoutes: [createRoute({ path: "/demo", handler: routeHandler })], | |
| }), | |
| log: createPluginLog(), | |
| }); | |
| const { res } = makeMockHttpResponse(); | |
| const handled = await handler({ url: "/demo" } as IncomingMessage, res); | |
| expect(handled).toBe(true); | |
| expect(routeHandler).toHaveBeenCalledTimes(1); | |
| }); | |
| it("prefers exact matches before prefix matches", async () => { | |
| const exactHandler = vi.fn(async (_req, res: ServerResponse) => { | |
| res.statusCode = 200; | |
| }); | |
| const prefixHandler = vi.fn(async () => true); | |
| const handler = createGatewayPluginRequestHandler({ | |
| registry: createGatewayTestRegistry({ | |
| httpRoutes: [ | |
| createRoute({ path: "/api", match: "prefix", handler: prefixHandler }), | |
| createRoute({ path: "/api/demo", match: "exact", handler: exactHandler }), | |
| ], | |
| }), | |
| log: createPluginLog(), | |
| }); | |
| const { res } = makeMockHttpResponse(); | |
| const handled = await handler({ url: "/api/demo" } as IncomingMessage, res); | |
| expect(handled).toBe(true); | |
| expect(exactHandler).toHaveBeenCalledTimes(1); | |
| expect(prefixHandler).not.toHaveBeenCalled(); | |
| }); | |
| it("supports route fallthrough when handler returns false", async () => { | |
| const first = vi.fn(async () => false); | |
| const second = vi.fn(async () => true); | |
| const handler = createGatewayPluginRequestHandler({ | |
| registry: createGatewayTestRegistry({ | |
| httpRoutes: [ | |
| createRoute({ path: "/hook", match: "exact", handler: first }), | |
| createRoute({ path: "/hook", match: "prefix", handler: second }), | |
| ], | |
| }), | |
| log: createPluginLog(), | |
| }); | |
| const { res } = makeMockHttpResponse(); | |
| const handled = await handler({ url: "/hook" } as IncomingMessage, res); | |
| expect(handled).toBe(true); | |
| expect(first).toHaveBeenCalledTimes(1); | |
| expect(second).toHaveBeenCalledTimes(1); | |
| }); | |
| it("fails closed when a matched gateway route reaches dispatch without auth", async () => { | |
| const { handled, exactPluginHandler, prefixGatewayHandler } = await invokeSecureGatewayRoute({ | |
| gatewayAuthSatisfied: false, | |
| }); | |
| expect(handled).toBe(false); | |
| expect(exactPluginHandler).not.toHaveBeenCalled(); | |
| expect(prefixGatewayHandler).not.toHaveBeenCalled(); | |
| }); | |
| it("keeps hosted-media bearer query strings out of route-auth logs", async () => { | |
| const warn = vi.fn(); | |
| const log = { warn } as unknown as PluginHandlerLog; | |
| const handler = createGatewayPluginRequestHandler({ | |
| registry: createGatewayTestRegistry({ | |
| httpRoutes: [createRoute({ path: "/webhooks/sms", auth: "gateway" })], | |
| }), | |
| log, | |
| }); | |
| const tokenParam = `__openclaw_mms_token_${"a".repeat(24)}`; | |
| const { res } = makeMockHttpResponse(); | |
| const handled = await handler( | |
| { | |
| url: `/webhooks/sms?upstream-token=proxy-secret&${tokenParam}=media-secret`, | |
| } as IncomingMessage, | |
| res, | |
| undefined, | |
| { gatewayAuthSatisfied: false }, | |
| ); | |
| expect(handled).toBe(false); | |
| expect(warn).toHaveBeenCalledWith( | |
| "plugin http route blocked without gateway auth (/webhooks/sms)", | |
| ); | |
| expect(JSON.stringify(warn.mock.calls)).not.toContain("proxy-secret"); | |
| expect(JSON.stringify(warn.mock.calls)).not.toContain("media-secret"); | |
| }); | |
| it("allows gateway route fallthrough only after gateway auth succeeds", async () => { | |
| const { handled, exactPluginHandler, prefixGatewayHandler } = await invokeSecureGatewayRoute({ | |
| gatewayAuthSatisfied: true, | |
| gatewayRequestOperatorScopes: ["operator.write"], | |
| }); | |
| expect(handled).toBe(true); | |
| expect(exactPluginHandler).toHaveBeenCalledTimes(1); | |
| expect(prefixGatewayHandler).toHaveBeenCalledTimes(1); | |
| }); | |
| it("fails closed when gateway route dispatch lacks caller scopes", async () => { | |
| const { handled, exactPluginHandler, prefixGatewayHandler } = await invokeSecureGatewayRoute({ | |
| gatewayAuthSatisfied: true, | |
| }); | |
| expect(handled).toBe(false); | |
| expect(exactPluginHandler).not.toHaveBeenCalled(); | |
| expect(prefixGatewayHandler).not.toHaveBeenCalled(); | |
| }); | |
| it("matches canonicalized route variants", async () => { | |
| const routeHandler = vi.fn(async (_req, res: ServerResponse) => { | |
| res.statusCode = 200; | |
| }); | |
| const handler = createGatewayPluginRequestHandler({ | |
| registry: createGatewayTestRegistry({ | |
| httpRoutes: [createRoute({ path: "/api/demo", handler: routeHandler })], | |
| }), | |
| log: createPluginLog(), | |
| }); | |
| const { res } = makeMockHttpResponse(); | |
| const handled = await handler({ url: "/API//demo" } as IncomingMessage, res); | |
| expect(handled).toBe(true); | |
| expect(routeHandler).toHaveBeenCalledTimes(1); | |
| }); | |
| it("uses the explicit registry when no route registry resolver is provided", async () => { | |
| const explicitRouteHandler = vi.fn(async (_req, res: ServerResponse) => { | |
| res.statusCode = 200; | |
| return true; | |
| }); | |
| const explicitRegistry = createGatewayTestRegistry({ | |
| httpRoutes: [createRoute({ path: "/demo", auth: "plugin", handler: explicitRouteHandler })], | |
| }); | |
| const handler = createGatewayPluginRequestHandler({ | |
| registry: explicitRegistry, | |
| log: createPluginLog(), | |
| }); | |
| const { res } = makeMockHttpResponse(); | |
| const handled = await handler({ url: "/demo" } as IncomingMessage, res); | |
| expect(handled).toBe(true); | |
| expect(explicitRouteHandler).toHaveBeenCalledTimes(1); | |
| }); | |
| it("logs and responds with 500 when a route throws", async () => { | |
| const log = createPluginLog(); | |
| const handler = createGatewayPluginRequestHandler({ | |
| registry: createGatewayTestRegistry({ | |
| httpRoutes: [ | |
| createRoute({ | |
| path: "/boom", | |
| handler: async () => { | |
| throw new Error("boom"); | |
| }, | |
| }), | |
| ], | |
| }), | |
| log, | |
| }); | |
| const { res, setHeader, end } = makeMockHttpResponse(); | |
| const handled = await handler({ url: "/boom" } as IncomingMessage, res); | |
| expect(handled).toBe(true); | |
| expect(log.warn).toHaveBeenCalledWith("plugin http route failed (route): Error: boom"); | |
| expect(res.statusCode).toBe(500); | |
| expect(setHeader).toHaveBeenCalledWith("Content-Type", "text/plain; charset=utf-8"); | |
| expect(end).toHaveBeenCalledWith("Internal Server Error"); | |
| }); | |
| it("aborts an incomplete unframed response when the plugin route throws", async () => { | |
| const log = createPluginLog(); | |
| const handler = createGatewayPluginRequestHandler({ | |
| registry: createGatewayTestRegistry({ | |
| httpRoutes: [ | |
| createRoute({ | |
| path: "/partial", | |
| handler: async (_req, res) => { | |
| res.writeHead(200, { "Content-Type": "text/plain; charset=utf-8" }); | |
| res.write("partial"); | |
| throw new Error("boom"); | |
| }, | |
| }), | |
| ], | |
| }), | |
| log, | |
| }); | |
| const server = createServer((req, res) => { | |
| void (async () => { | |
| const handled = await handler(req, res); | |
| if (!handled) { | |
| res.statusCode = 404; | |
| res.end("not found"); | |
| } | |
| })(); | |
| }); | |
| await new Promise<void>((resolve, reject) => { | |
| server.once("error", reject); | |
| server.listen(0, "127.0.0.1", () => resolve()); | |
| }); | |
| const address = server.address(); | |
| if (!address || typeof address === "string") { | |
| throw new Error("server did not bind to a TCP port"); | |
| } | |
| try { | |
| await expect( | |
| fetch(`http://127.0.0.1:${address.port}/partial`, { | |
| signal: AbortSignal.timeout(1_000), | |
| }).then(async (response) => await response.text()), | |
| ).rejects.toMatchObject({ name: "TypeError" }); | |
| expect(log.warn).toHaveBeenCalledWith("plugin http route failed (route): Error: boom"); | |
| } finally { | |
| server.closeAllConnections(); | |
| await new Promise<void>((resolve) => { | |
| server.close(() => resolve()); | |
| }); | |
| } | |
| }); | |
| it.each([ | |
| { | |
| label: "setHeader", | |
| setContentLength: (res: ServerResponse) => res.setHeader("Content-Length", "10"), | |
| }, | |
| { | |
| label: "writeHead", | |
| setContentLength: (res: ServerResponse) => res.writeHead(200, { "Content-Length": "10" }), | |
| }, | |
| ])( | |
| "closes an incomplete plugin $label fixed-length response after its route throws", | |
| async ({ setContentLength }) => { | |
| const log = createPluginLog(); | |
| const handler = createGatewayPluginRequestHandler({ | |
| registry: createGatewayTestRegistry({ | |
| httpRoutes: [ | |
| createRoute({ | |
| path: "/incomplete", | |
| handler: async (_req, res) => { | |
| setContentLength(res); | |
| res.write("partial"); | |
| throw new Error("boom"); | |
| }, | |
| }), | |
| ], | |
| }), | |
| log, | |
| }); | |
| const server = createServer((req, res) => { | |
| void handler(req, res); | |
| }); | |
| await new Promise<void>((resolve, reject) => { | |
| server.once("error", reject); | |
| server.listen(0, "127.0.0.1", () => resolve()); | |
| }); | |
| const address = server.address(); | |
| if (!address || typeof address === "string") { | |
| throw new Error("server did not bind to a TCP port"); | |
| } | |
| try { | |
| await expect( | |
| fetch(`http://127.0.0.1:${address.port}/incomplete`, { | |
| signal: AbortSignal.timeout(500), | |
| }).then(async (response) => await response.text()), | |
| ).rejects.toMatchObject({ name: "TypeError" }); | |
| } finally { | |
| server.closeAllConnections(); | |
| await new Promise<void>((resolve) => { | |
| server.close(() => resolve()); | |
| }); | |
| } | |
| }, | |
| ); | |
| it("does not end a response the plugin already destroyed before throwing", async () => { | |
| const log = createPluginLog(); | |
| const handler = createGatewayPluginRequestHandler({ | |
| registry: createGatewayTestRegistry({ | |
| httpRoutes: [ | |
| createRoute({ | |
| path: "/destroyed", | |
| handler: async (_req, res) => { | |
| Object.defineProperty(res, "headersSent", { value: true, configurable: true }); | |
| res.destroy(); | |
| throw new Error("boom"); | |
| }, | |
| }), | |
| ], | |
| }), | |
| log, | |
| }); | |
| const { res, end } = makeMockHttpResponse(); | |
| const handled = await handler({ url: "/destroyed" } as IncomingMessage, res); | |
| expect(handled).toBe(true); | |
| expect(res.destroyed).toBe(true); | |
| expect(end).not.toHaveBeenCalled(); | |
| expect(log.warn).toHaveBeenCalledWith("plugin http route failed (route): Error: boom"); | |
| }); | |
| }); | |
| describe("createGatewayPluginUpgradeHandler", () => { | |
| afterEach(() => { | |
| setActivePluginRegistry(createEmptyPluginRegistry()); | |
| }); | |
| it("claims and rejects matched gateway upgrades when auth was not satisfied", async () => { | |
| const { handled, routeUpgradeHandler, socket } = await invokeCanvasGatewayUpgrade({ | |
| gatewayAuthSatisfied: false, | |
| }); | |
| expect(handled).toBe(true); | |
| expect(routeUpgradeHandler).not.toHaveBeenCalled(); | |
| expect(socket.destroyed).toBe(true); | |
| expect(socket.chunks.join("")).toContain("HTTP/1.1 401 Unauthorized"); | |
| }); | |
| it("dispatches gateway upgrades after gateway auth succeeds", async () => { | |
| const { handled, routeUpgradeHandler, socket } = await invokeCanvasGatewayUpgrade({ | |
| gatewayAuthSatisfied: true, | |
| }); | |
| expect(handled).toBe(true); | |
| expect(routeUpgradeHandler).toHaveBeenCalledTimes(1); | |
| expect(socket.destroyed).toBe(false); | |
| expect(socket.chunks).toStrictEqual([]); | |
| }); | |
| }); | |
| describe("plugin HTTP route auth checks", () => { | |
| const deeplyEncodedChannelPath = | |
| "/api%2525252fchannels%2525252fnostr%2525252fdefault%2525252fprofile"; | |
| const decodeOverflowPublicPath = `/googlechat${buildRepeatedEncodedSlash(40)}public`; | |
| it("detects registered route paths", () => { | |
| const registry = createGatewayTestRegistry({ | |
| httpRoutes: [createRoute({ path: "/demo" })], | |
| }); | |
| expect(isRegisteredPluginHttpRoutePath(registry, "/demo")).toBe(true); | |
| expect(isRegisteredPluginHttpRoutePath(registry, "/missing")).toBe(false); | |
| }); | |
| it("matches canonicalized variants of registered route paths", () => { | |
| const registry = createGatewayTestRegistry({ | |
| httpRoutes: [createRoute({ path: "/api/demo" })], | |
| }); | |
| expect(isRegisteredPluginHttpRoutePath(registry, "/api//demo")).toBe(true); | |
| expect(isRegisteredPluginHttpRoutePath(registry, "/API/demo")).toBe(true); | |
| expect(isRegisteredPluginHttpRoutePath(registry, "/api/%2564emo")).toBe(true); | |
| }); | |
| it("enforces auth for protected and gateway-auth routes", () => { | |
| const registry = createGatewayTestRegistry({ | |
| httpRoutes: [ | |
| createRoute({ path: "/googlechat", match: "prefix", auth: "plugin" }), | |
| createRoute({ path: "/api/demo", auth: "gateway" }), | |
| ], | |
| }); | |
| expect(shouldEnforceGatewayAuthForPluginPath(registry, "/api//demo")).toBe(true); | |
| expect(shouldEnforceGatewayAuthForPluginPath(registry, "/googlechat/public")).toBe(false); | |
| expect(shouldEnforceGatewayAuthForPluginPath(registry, "/api/channels/status")).toBe(true); | |
| expect(shouldEnforceGatewayAuthForPluginPath(registry, deeplyEncodedChannelPath)).toBe(true); | |
| expect(shouldEnforceGatewayAuthForPluginPath(registry, decodeOverflowPublicPath)).toBe(true); | |
| expect(shouldEnforceGatewayAuthForPluginPath(registry, "/not-plugin")).toBe(false); | |
| }); | |
| it("enforces auth when any overlapping matched route requires gateway auth", () => { | |
| const registry = createGatewayTestRegistry({ | |
| httpRoutes: [ | |
| createRoute({ path: "/plugin/secure/report", match: "exact", auth: "plugin" }), | |
| createRoute({ path: "/plugin/secure", match: "prefix", auth: "gateway" }), | |
| ], | |
| }); | |
| expect(shouldEnforceGatewayAuthForPluginPath(registry, "/plugin/secure/report")).toBe(true); | |
| }); | |
| it("recognizes only existing, unambiguous plugin-authenticated routes", () => { | |
| const registry = createGatewayTestRegistry({ | |
| httpRoutes: [ | |
| createRoute({ path: "/googlechat", match: "prefix", auth: "plugin" }), | |
| createRoute({ path: "/plugin/secure", match: "prefix", auth: "gateway" }), | |
| createRoute({ path: "/plugin/secure/report", auth: "plugin" }), | |
| ], | |
| }); | |
| expect(isPluginAuthenticatedRoutePath(registry, "/googlechat")).toBe(true); | |
| expect(isPluginAuthenticatedRoutePath(registry, "/googlechat/events")).toBe(true); | |
| expect(isPluginAuthenticatedRoutePath(registry, "/missing")).toBe(false); | |
| expect(isPluginAuthenticatedRoutePath(registry, "/api/channels/status")).toBe(false); | |
| expect(isPluginAuthenticatedRoutePath(registry, "/plugin/secure/report")).toBe(false); | |
| expect(isPluginAuthenticatedRoutePath(registry, decodeOverflowPublicPath)).toBe(false); | |
| }); | |
| }); | |