refactor: share plugin runtime scope test setup

This commit is contained in:
Vincent Koc
2026-06-01 11:07:29 +02:00
parent 9cb052ccef
commit e85be626a4

View File

@@ -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<typeof createRoute>;
type PluginRequestHandler = ReturnType<typeof createGatewayPluginRequestHandler>;
type PluginRequestAuthContext = NonNullable<Parameters<PluginRequestHandler>[3]>;
function createRoute(params: {
path: string;
auth: "gateway" | "plugin";
@@ -71,6 +78,64 @@ function assertAdminHelperAllowed() {
}
}
function createPluginRequestHandler(params: {
routes: PluginHttpRoute[];
log?: SubsystemLogger;
getRouteRegistry?: () => ReturnType<typeof createTestRegistry>;
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<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(() => {
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);