import { afterEach, describe, expect, it, vi } from "vitest"; import { createEmptyPluginRegistry } from "../plugins/registry-empty.js"; import { setActivePluginRegistry } from "../plugins/runtime.js"; import { createGatewayMethodRegistry, createPluginGatewayMethodDescriptor, } from "./methods/registry.js"; import { handleGatewayRequest } from "./server-methods.js"; import type { GatewayRequestHandler } from "./server-methods/types.js"; const METHOD = "workboard.cards.dispatch"; afterEach(() => { setActivePluginRegistry(createEmptyPluginRegistry()); }); describe("gateway method authorization", () => { async function dispatch(scopes: string[]) { const handler: GatewayRequestHandler = ({ respond }) => respond(true, { ok: true }); const methodRegistry = createGatewayMethodRegistry([ createPluginGatewayMethodDescriptor({ pluginId: "workboard", name: METHOD, handler, scope: "operator.write", }), ]); const respond = vi.fn(); // Reproduce a request whose attached dispatch registry is newer than the global runtime state. setActivePluginRegistry(createEmptyPluginRegistry()); await handleGatewayRequest({ req: { type: "req", id: "req-1", method: METHOD }, respond, client: { connId: "conn-1", connect: { role: "operator", scopes, client: { id: "test", version: "1", platform: "test", mode: "test" }, minProtocol: 1, maxProtocol: 1, }, } as Parameters[0]["client"], isWebchatConnect: () => false, context: { logGateway: { warn: vi.fn() } } as unknown as Parameters< typeof handleGatewayRequest >[0]["context"], methodRegistry, }); return respond; } it("authorizes from the attached registry used for dispatch", async () => { const allowed = await dispatch(["operator.write"]); const denied = await dispatch(["operator.read"]); expect(allowed).toHaveBeenCalledWith(true, { ok: true }); expect(denied).toHaveBeenCalledWith( false, undefined, expect.objectContaining({ message: "missing scope: operator.write" }), ); }); });