Tests: align approval gateway seams

This commit is contained in:
Gustavo Madeira Santana
2026-04-07 16:04:17 -04:00
parent 28fc5d9b5e
commit ecc9a65f34
4 changed files with 61 additions and 54 deletions

View File

@@ -4,9 +4,9 @@ import { beforeEach, describe, expect, it, vi } from "vitest";
const resolveApprovalOverGatewayMock = vi.hoisted(() => vi.fn());
vi.mock("openclaw/plugin-sdk/approval-handler-runtime", async (importOriginal) => {
vi.mock("openclaw/plugin-sdk/approval-gateway-runtime", async (importOriginal) => {
const actual =
await importOriginal<typeof import("openclaw/plugin-sdk/approval-handler-runtime")>();
await importOriginal<typeof import("openclaw/plugin-sdk/approval-gateway-runtime")>();
return {
...actual,
resolveApprovalOverGateway: resolveApprovalOverGatewayMock,

View File

@@ -4,7 +4,7 @@ const approvalRuntimeHoisted = vi.hoisted(() => ({
resolveApprovalOverGatewaySpy: vi.fn(),
}));
vi.mock("openclaw/plugin-sdk/approval-handler-runtime", () => ({
vi.mock("openclaw/plugin-sdk/approval-gateway-runtime", () => ({
resolveApprovalOverGateway: (...args: unknown[]) =>
approvalRuntimeHoisted.resolveApprovalOverGatewaySpy(...args),
}));

View File

@@ -1,22 +1,19 @@
import { beforeEach, describe, expect, it, vi } from "vitest";
const gatewayRuntimeHoisted = vi.hoisted(() => ({
requestSpy: vi.fn(),
withClientSpy: vi.fn(),
const approvalGatewayRuntimeHoisted = vi.hoisted(() => ({
resolveApprovalOverGatewaySpy: vi.fn(),
}));
vi.mock("openclaw/plugin-sdk/gateway-runtime", () => ({
withOperatorApprovalsGatewayClient: gatewayRuntimeHoisted.withClientSpy,
vi.mock("openclaw/plugin-sdk/approval-gateway-runtime", () => ({
resolveApprovalOverGateway: (...args: unknown[]) =>
approvalGatewayRuntimeHoisted.resolveApprovalOverGatewaySpy(...args),
}));
describe("resolveTelegramExecApproval", () => {
beforeEach(() => {
gatewayRuntimeHoisted.requestSpy.mockReset();
gatewayRuntimeHoisted.withClientSpy.mockReset().mockImplementation(async (_params, run) => {
await run({
request: gatewayRuntimeHoisted.requestSpy,
} as never);
});
approvalGatewayRuntimeHoisted.resolveApprovalOverGatewaySpy
.mockReset()
.mockResolvedValue(undefined);
});
it("routes plugin approval ids through plugin.approval.resolve", async () => {
@@ -29,16 +26,18 @@ describe("resolveTelegramExecApproval", () => {
senderId: "9",
});
expect(gatewayRuntimeHoisted.requestSpy).toHaveBeenCalledWith("plugin.approval.resolve", {
id: "plugin:abc123",
expect(approvalGatewayRuntimeHoisted.resolveApprovalOverGatewaySpy).toHaveBeenCalledWith({
cfg: {} as never,
approvalId: "plugin:abc123",
decision: "allow-once",
senderId: "9",
gatewayUrl: undefined,
allowPluginFallback: undefined,
clientDisplayName: "Telegram approval (9)",
});
});
it("falls back to plugin.approval.resolve when exec approval ids are unknown", async () => {
gatewayRuntimeHoisted.requestSpy
.mockRejectedValueOnce(new Error("unknown or expired approval id"))
.mockResolvedValueOnce(undefined);
const { resolveTelegramExecApproval } = await import("./exec-approval-resolver.js");
await resolveTelegramExecApproval({
@@ -49,24 +48,18 @@ describe("resolveTelegramExecApproval", () => {
allowPluginFallback: true,
});
expect(gatewayRuntimeHoisted.requestSpy).toHaveBeenNthCalledWith(1, "exec.approval.resolve", {
id: "legacy-plugin-123",
decision: "allow-always",
});
expect(gatewayRuntimeHoisted.requestSpy).toHaveBeenNthCalledWith(2, "plugin.approval.resolve", {
id: "legacy-plugin-123",
expect(approvalGatewayRuntimeHoisted.resolveApprovalOverGatewaySpy).toHaveBeenCalledWith({
cfg: {} as never,
approvalId: "legacy-plugin-123",
decision: "allow-always",
senderId: "9",
gatewayUrl: undefined,
allowPluginFallback: true,
clientDisplayName: "Telegram approval (9)",
});
});
it("falls back to plugin.approval.resolve for structured approval-not-found errors", async () => {
const err = new Error("approval not found");
(err as Error & { gatewayCode?: string; details?: { reason?: string } }).gatewayCode =
"INVALID_REQUEST";
(err as Error & { gatewayCode?: string; details?: { reason?: string } }).details = {
reason: "APPROVAL_NOT_FOUND",
};
gatewayRuntimeHoisted.requestSpy.mockRejectedValueOnce(err).mockResolvedValueOnce(undefined);
const { resolveTelegramExecApproval } = await import("./exec-approval-resolver.js");
await resolveTelegramExecApproval({
@@ -77,35 +70,35 @@ describe("resolveTelegramExecApproval", () => {
allowPluginFallback: true,
});
expect(gatewayRuntimeHoisted.requestSpy).toHaveBeenNthCalledWith(1, "exec.approval.resolve", {
id: "legacy-plugin-123",
decision: "allow-always",
});
expect(gatewayRuntimeHoisted.requestSpy).toHaveBeenNthCalledWith(2, "plugin.approval.resolve", {
id: "legacy-plugin-123",
expect(approvalGatewayRuntimeHoisted.resolveApprovalOverGatewaySpy).toHaveBeenCalledWith({
cfg: {} as never,
approvalId: "legacy-plugin-123",
decision: "allow-always",
senderId: "9",
gatewayUrl: undefined,
allowPluginFallback: true,
clientDisplayName: "Telegram approval (9)",
});
});
it("does not fall back to plugin.approval.resolve without explicit permission", async () => {
gatewayRuntimeHoisted.requestSpy.mockRejectedValueOnce(
new Error("unknown or expired approval id"),
);
it("passes fallback disablement through unchanged", async () => {
const { resolveTelegramExecApproval } = await import("./exec-approval-resolver.js");
await expect(
resolveTelegramExecApproval({
cfg: {} as never,
approvalId: "legacy-plugin-123",
decision: "allow-always",
senderId: "9",
}),
).rejects.toThrow("unknown or expired approval id");
expect(gatewayRuntimeHoisted.requestSpy).toHaveBeenCalledTimes(1);
expect(gatewayRuntimeHoisted.requestSpy).toHaveBeenCalledWith("exec.approval.resolve", {
id: "legacy-plugin-123",
await resolveTelegramExecApproval({
cfg: {} as never,
approvalId: "legacy-plugin-123",
decision: "allow-always",
senderId: "9",
});
expect(approvalGatewayRuntimeHoisted.resolveApprovalOverGatewaySpy).toHaveBeenCalledWith({
cfg: {} as never,
approvalId: "legacy-plugin-123",
decision: "allow-always",
senderId: "9",
gatewayUrl: undefined,
allowPluginFallback: undefined,
clientDisplayName: "Telegram approval (9)",
});
});
});

View File

@@ -92,6 +92,20 @@ describe("runtime import side-effect contracts", () => {
expectNoChannelRegistryDuringImport("src/plugins/runtime/runtime-channel.ts");
});
it("keeps plugin-sdk/approval-handler-adapter-runtime cold on import", async () => {
mockChannelRegistry();
await import("../../plugin-sdk/approval-handler-adapter-runtime.js");
expectNoChannelRegistryDuringImport("src/plugin-sdk/approval-handler-adapter-runtime.ts");
});
it("keeps plugin-sdk/approval-gateway-runtime cold on import", async () => {
mockChannelRegistry();
await import("../../plugin-sdk/approval-gateway-runtime.js");
expectNoChannelRegistryDuringImport("src/plugin-sdk/approval-gateway-runtime.ts");
});
it("keeps plugins/runtime/runtime-system cold on import", async () => {
mockChannelRegistry();
await import("../runtime/runtime-system.js");