Quellcodebibliothek Statistik Leitseite products/Sources/formale Sprachen/Java/Openclaw/src/infra/   (KI Agentensystem Version 22©)  Datei vom 26.3.2026 mit Größe 22 kB image not shown  

Quelle  exec-approval-channel-runtime.test.ts

  Sprache: JAVA
 

Spracherkennung für: .ts vermutete Sprache: Unknown {[0] [0] [0]} [Methode: Schwerpunktbildung, einfache Gewichte, sechs Dimensionen]

import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
import type { GatewayClient } from "../gateway/client.js";
import type { ExecApprovalRequest } from "./exec-approvals.js";
import type { PluginApprovalRequest, PluginApprovalResolved } from "./plugin-approvals.js";

const mockGatewayClientStarts = vi.hoisted(() => vi.fn());
const mockGatewayClientStops = vi.hoisted(() => vi.fn());
const mockGatewayClientRequests = vi.hoisted(() =>
  vi.fn<(method: string, params?: Record<string, unknown>) => Promise<unknown>>(async () => ({
    ok: true,
  })),
);
const mockCreateOperatorApprovalsGatewayClient = vi.hoisted(() => vi.fn());
const loggerMocks = vi.hoisted(() => ({
  debug: vi.fn(),
  error: vi.fn(),
}));

vi.mock("../gateway/operator-approvals-client.js", () => ({
  createOperatorApprovalsGatewayClient: mockCreateOperatorApprovalsGatewayClient,
}));

vi.mock("../logging/subsystem.js", () => ({
  createSubsystemLogger: () => loggerMocks,
}));

let createExecApprovalChannelRuntime: typeof import("./exec-approval-channel-runtime.js").createExecApprovalChannelRuntime;

function createDeferred<T>() {
  let resolve!: (value: T | PromiseLike<T>) => void;
  let reject!: (reason?: unknown) => void;
  const promise = new Promise<T>((promiseResolve, promiseReject) => {
    resolve = promiseResolve;
    reject = promiseReject;
  });
  return { promise, resolve, reject };
}

type GatewayEventClientParams = { onEvent?: (evt: { event: string; payload: unknown }) => void };

function lastGatewayEventClientParams(): GatewayEventClientParams | undefined {
  return mockCreateOperatorApprovalsGatewayClient.mock.calls[0]?.[0] as
    | GatewayEventClientParams
    | undefined;
}

function emitPluginApprovalRequested(clientParams = lastGatewayEventClientParams()) {
  clientParams?.onEvent?.({
    event: "plugin.approval.requested",
    payload: createPluginReplayRequest("plugin:abc"),
  });
}

function createExecReplayRequest(id = "abc"): ExecApprovalRequest {
  return {
    id,
    request: {
      command: "echo abc",
    },
    createdAtMs: 1000,
    expiresAtMs: 2000,
  };
}

function createPluginReplayRequest(id = "plugin:abc"): PluginApprovalRequest {
  return {
    id,
    request: {
      title: "Plugin approval",
      description: "Let plugin proceed",
    },
    createdAtMs: 1000,
    expiresAtMs: 2000,
  };
}

function mockReplayLists(params: {
  exec?: ExecApprovalRequest[];
  plugin?: PluginApprovalRequest[];
}) {
  mockGatewayClientRequests.mockImplementation(async (method: string) => {
    if (method === "exec.approval.list") {
      return params.exec ?? [];
    }
    if (method === "plugin.approval.list") {
      return params.plugin ?? [];
    }
    return { ok: true };
  });
}

beforeEach(() => {
  mockGatewayClientStarts.mockReset();
  mockGatewayClientStops.mockReset();
  mockGatewayClientRequests.mockReset();
  mockGatewayClientRequests.mockImplementation(async (method: string) =>
    method.endsWith(".approval.list") ? [] : { ok: true },
  );
  loggerMocks.debug.mockReset();
  loggerMocks.error.mockReset();
  mockCreateOperatorApprovalsGatewayClient.mockReset().mockImplementation(async (params) => ({
    start: () => {
      mockGatewayClientStarts();
      queueMicrotask(() => {
        params.onHelloOk?.({ type: "hello-ok" } as never);
      });
    },
    stop: mockGatewayClientStops,
    request: mockGatewayClientRequests,
  }));
});

afterEach(() => {
  vi.restoreAllMocks();
  vi.useRealTimers();
});

beforeAll(async () => {
  ({ createExecApprovalChannelRuntime } = await import("./exec-approval-channel-runtime.js"));
});

describe("createExecApprovalChannelRuntime", () => {
  it("does not connect when the adapter is not configured", async () => {
    const runtime = createExecApprovalChannelRuntime({
      label: "test/exec-approvals",
      clientDisplayName: "Test Exec Approvals",
      cfg: {} as never,
      isConfigured: () => false,
      shouldHandle: () => true,
      deliverRequested: async () => [],
      finalizeResolved: async () => undefined,
    });

    await runtime.start();

    expect(mockCreateOperatorApprovalsGatewayClient).not.toHaveBeenCalled();
  });

  it("tracks pending requests and only expires the matching approval id", async () => {
    vi.useFakeTimers();
    const finalizedExpired = vi.fn(async () => undefined);
    const finalizedResolved = vi.fn(async () => undefined);
    const runtime = createExecApprovalChannelRuntime({
      label: "test/exec-approvals",
      clientDisplayName: "Test Exec Approvals",
      cfg: {} as never,
      nowMs: () => 1000,
      isConfigured: () => true,
      shouldHandle: () => true,
      deliverRequested: async (request) => [{ id: request.id }],
      finalizeResolved: finalizedResolved,
      finalizeExpired: finalizedExpired,
    });

    await runtime.handleRequested({
      id: "abc",
      request: {
        command: "echo abc",
      },
      createdAtMs: 1000,
      expiresAtMs: 2000,
    });
    await runtime.handleRequested({
      id: "xyz",
      request: {
        command: "echo xyz",
      },
      createdAtMs: 1000,
      expiresAtMs: 2000,
    });

    await runtime.handleExpired("abc");

    expect(finalizedExpired).toHaveBeenCalledTimes(1);
    expect(finalizedExpired).toHaveBeenCalledWith({
      request: expect.objectContaining({ id: "abc" }),
      entries: [{ id: "abc" }],
    });
    expect(finalizedResolved).not.toHaveBeenCalled();

    await runtime.handleResolved({
      id: "xyz",
      decision: "allow-once",
      ts: 1500,
    });

    expect(finalizedResolved).toHaveBeenCalledTimes(1);
    expect(finalizedResolved).toHaveBeenCalledWith({
      request: expect.objectContaining({ id: "xyz" }),
      resolved: expect.objectContaining({ id: "xyz", decision: "allow-once" }),
      entries: [{ id: "xyz" }],
    });
  });

  it("finalizes approvals that resolve while delivery is still in flight", async () => {
    const pendingDelivery = createDeferred<Array<{ id: string }>>();
    const finalizeResolved = vi.fn(async () => undefined);
    const runtime = createExecApprovalChannelRuntime<
      { id: string },
      PluginApprovalRequest,
      PluginApprovalResolved
    >({
      label: "test/plugin-approvals",
      clientDisplayName: "Test Plugin Approvals",
      cfg: {} as never,
      eventKinds: ["plugin"],
      isConfigured: () => true,
      shouldHandle: () => true,
      deliverRequested: async () => pendingDelivery.promise,
      finalizeResolved,
    });

    const requestPromise = runtime.handleRequested({
      id: "plugin:abc",
      request: {
        title: "Plugin approval",
        description: "Let plugin proceed",
      },
      createdAtMs: 1000,
      expiresAtMs: 2000,
    });
    await runtime.handleResolved({
      id: "plugin:abc",
      decision: "allow-once",
      ts: 1500,
    });

    pendingDelivery.resolve([{ id: "plugin:abc" }]);
    await requestPromise;

    expect(finalizeResolved).toHaveBeenCalledWith({
      request: expect.objectContaining({ id: "plugin:abc" }),
      resolved: expect.objectContaining({ id: "plugin:abc", decision: "allow-once" }),
      entries: [{ id: "plugin:abc" }],
    });
  });

  it("routes gateway requests through the shared client", async () => {
    const runtime = createExecApprovalChannelRuntime({
      label: "test/exec-approvals",
      clientDisplayName: "Test Exec Approvals",
      cfg: {} as never,
      isConfigured: () => true,
      shouldHandle: () => true,
      deliverRequested: async () => [],
      finalizeResolved: async () => undefined,
    });

    await runtime.start();
    await runtime.request("exec.approval.resolve", { id: "abc", decision: "deny" });

    expect(mockGatewayClientStarts).toHaveBeenCalledTimes(1);
    expect(mockGatewayClientRequests).toHaveBeenCalledWith("exec.approval.resolve", {
      id: "abc",
      decision: "deny",
    });
  });

  it("can retry start after gateway client creation fails", async () => {
    const boom = new Error("boom");
    mockCreateOperatorApprovalsGatewayClient
      .mockRejectedValueOnce(boom)
      .mockImplementationOnce(async (params) => ({
        start: () => {
          mockGatewayClientStarts();
          queueMicrotask(() => {
            params.onHelloOk?.({ type: "hello-ok" } as never);
          });
        },
        stop: mockGatewayClientStops,
        request: mockGatewayClientRequests,
      }));
    const runtime = createExecApprovalChannelRuntime({
      label: "test/exec-approvals",
      clientDisplayName: "Test Exec Approvals",
      cfg: {} as never,
      isConfigured: () => true,
      shouldHandle: () => true,
      deliverRequested: async () => [],
      finalizeResolved: async () => undefined,
    });

    await expect(runtime.start()).rejects.toThrow("boom");
    await runtime.start();

    expect(mockCreateOperatorApprovalsGatewayClient).toHaveBeenCalledTimes(2);
    expect(mockGatewayClientStarts).toHaveBeenCalledTimes(1);
  });

  it("does not leave a gateway client running when stop wins the startup race", async () => {
    const pendingClient = createDeferred<GatewayClient>();
    mockCreateOperatorApprovalsGatewayClient.mockReturnValueOnce(pendingClient.promise);
    const runtime = createExecApprovalChannelRuntime({
      label: "test/exec-approvals",
      clientDisplayName: "Test Exec Approvals",
      cfg: {} as never,
      isConfigured: () => true,
      shouldHandle: () => true,
      deliverRequested: async () => [],
      finalizeResolved: async () => undefined,
    });

    const startPromise = runtime.start();
    const stopPromise = runtime.stop();
    pendingClient.resolve({
      start: mockGatewayClientStarts,
      stop: mockGatewayClientStops,
      request: mockGatewayClientRequests as GatewayClient["request"],
    } as unknown as GatewayClient);
    await startPromise;
    await stopPromise;

    expect(mockGatewayClientStarts).not.toHaveBeenCalled();
    expect(mockGatewayClientStops).toHaveBeenCalledTimes(1);
    await expect(runtime.request("exec.approval.resolve", { id: "abc" })).rejects.toThrow(
      "gateway client not connected",
    );
  });

  it("logs async request handling failures from gateway events", async () => {
    const runtime = createExecApprovalChannelRuntime<
      { id: string },
      PluginApprovalRequest,
      PluginApprovalResolved
    >({
      label: "test/plugin-approvals",
      clientDisplayName: "Test Plugin Approvals",
      cfg: {} as never,
      eventKinds: ["plugin"],
      isConfigured: () => true,
      shouldHandle: () => true,
      deliverRequested: async () => {
        throw new Error("deliver failed");
      },
      finalizeResolved: async () => undefined,
    });

    await runtime.start();
    emitPluginApprovalRequested();

    await vi.waitFor(() => {
      expect(loggerMocks.error).toHaveBeenCalledWith(
        "error handling approval request: deliver failed",
      );
    });
  });

  it("logs async expiration handling failures", async () => {
    vi.useFakeTimers();
    const runtime = createExecApprovalChannelRuntime<
      { id: string },
      PluginApprovalRequest,
      PluginApprovalResolved
    >({
      label: "test/plugin-approvals",
      clientDisplayName: "Test Plugin Approvals",
      cfg: {} as never,
      nowMs: () => 1000,
      eventKinds: ["plugin"],
      isConfigured: () => true,
      shouldHandle: () => true,
      deliverRequested: async (request) => [{ id: request.id }],
      finalizeResolved: async () => undefined,
      finalizeExpired: async () => {
        throw new Error("expire failed");
      },
    });

    await runtime.handleRequested({
      id: "plugin:abc",
      request: {
        title: "Plugin approval",
        description: "Let plugin proceed",
      },
      createdAtMs: 1000,
      expiresAtMs: 1001,
    });
    await vi.advanceTimersByTimeAsync(1);

    expect(loggerMocks.error).toHaveBeenCalledWith(
      "error handling approval expiration: expire failed",
    );
  });

  it("subscribes to plugin approval events when requested", async () => {
    const deliverRequested = vi.fn(async (request) => [{ id: request.id }]);
    const finalizeResolved = vi.fn(async () => undefined);
    const runtime = createExecApprovalChannelRuntime<
      { id: string },
      PluginApprovalRequest,
      PluginApprovalResolved
    >({
      label: "test/plugin-approvals",
      clientDisplayName: "Test Plugin Approvals",
      cfg: {} as never,
      eventKinds: ["plugin"],
      isConfigured: () => true,
      shouldHandle: () => true,
      deliverRequested,
      finalizeResolved,
    });

    await runtime.start();
    const clientParams = lastGatewayEventClientParams();
    expect(clientParams?.onEvent).toBeTypeOf("function");

    emitPluginApprovalRequested(clientParams);
    await vi.waitFor(() => {
      expect(deliverRequested).toHaveBeenCalledWith(
        expect.objectContaining({
          id: "plugin:abc",
        }),
      );
    });

    clientParams?.onEvent?.({
      event: "plugin.approval.resolved",
      payload: {
        id: "plugin:abc",
        decision: "allow-once",
        ts: 1500,
      },
    });
    await vi.waitFor(() => {
      expect(finalizeResolved).toHaveBeenCalledWith({
        request: expect.objectContaining({ id: "plugin:abc" }),
        resolved: expect.objectContaining({ id: "plugin:abc", decision: "allow-once" }),
        entries: [{ id: "plugin:abc" }],
      });
    });
  });

  it("replays pending approvals after the gateway connection is ready", async () => {
    mockReplayLists({ exec: [createExecReplayRequest()] });
    const deliverRequested = vi.fn(async (request) => [{ id: request.id }]);
    const runtime = createExecApprovalChannelRuntime({
      label: "test/replay",
      clientDisplayName: "Test Replay",
      cfg: {} as never,
      isConfigured: () => true,
      shouldHandle: () => true,
      deliverRequested,
      finalizeResolved: async () => undefined,
    });

    await runtime.start();

    await vi.waitFor(() => {
      expect(mockGatewayClientRequests).toHaveBeenCalledWith("exec.approval.list", {});
      expect(deliverRequested).toHaveBeenCalledWith(
        expect.objectContaining({
          id: "abc",
        }),
      );
    });
  });

  it("does not block start on pending approval replay delivery", async () => {
    mockReplayLists({ exec: [createExecReplayRequest()] });
    const pendingDelivery = createDeferred<Array<{ id: string }>>();
    const deliverRequested = vi.fn(async () => pendingDelivery.promise);
    const runtime = createExecApprovalChannelRuntime({
      label: "test/replay-start",
      clientDisplayName: "Test Replay Start",
      cfg: {} as never,
      nowMs: () => 1000,
      isConfigured: () => true,
      shouldHandle: () => true,
      deliverRequested,
      finalizeResolved: async () => undefined,
    });

    await runtime.start();

    await vi.waitFor(() => {
      expect(deliverRequested).toHaveBeenCalledWith(
        expect.objectContaining({
          id: "abc",
        }),
      );
    });
    pendingDelivery.resolve([{ id: "abc" }]);
    await runtime.stop();
  });

  it("ignores live duplicate approval events after replay", async () => {
    mockReplayLists({ plugin: [createPluginReplayRequest()] });
    const deliverRequested = vi.fn(async (request) => [{ id: request.id }]);
    const runtime = createExecApprovalChannelRuntime<
      { id: string },
      PluginApprovalRequest,
      PluginApprovalResolved
    >({
      label: "test/plugin-replay",
      clientDisplayName: "Test Plugin Replay",
      cfg: {} as never,
      eventKinds: ["plugin"],
      isConfigured: () => true,
      shouldHandle: () => true,
      deliverRequested,
      finalizeResolved: async () => undefined,
    });

    await runtime.start();
    await vi.waitFor(() => {
      expect(deliverRequested).toHaveBeenCalledTimes(1);
    });
    emitPluginApprovalRequested();
    await Promise.resolve();

    expect(deliverRequested).toHaveBeenCalledTimes(1);
  });

  it("ignores live duplicate approval events while replay delivery is still in flight", async () => {
    mockReplayLists({ plugin: [createPluginReplayRequest()] });
    const pendingDelivery = createDeferred<Array<{ id: string }>>();
    const deliverRequested = vi.fn(async () => pendingDelivery.promise);
    const runtime = createExecApprovalChannelRuntime<
      { id: string },
      PluginApprovalRequest,
      PluginApprovalResolved
    >({
      label: "test/plugin-replay-live-duplicate",
      clientDisplayName: "Test Plugin Replay Live Duplicate",
      cfg: {} as never,
      nowMs: () => 1000,
      eventKinds: ["plugin"],
      isConfigured: () => true,
      shouldHandle: () => true,
      deliverRequested,
      finalizeResolved: async () => undefined,
    });

    await runtime.start();
    await vi.waitFor(() => {
      expect(deliverRequested).toHaveBeenCalledTimes(1);
    });

    emitPluginApprovalRequested();
    await Promise.resolve();
    expect(deliverRequested).toHaveBeenCalledTimes(1);

    pendingDelivery.resolve([{ id: "plugin:abc" }]);
    await runtime.stop();
  });

  it("does not replay approvals after stop wins once hello is already complete", async () => {
    const replayDeferred = createDeferred<ExecApprovalRequest[]>();
    mockGatewayClientRequests.mockImplementation(async (method: string) => {
      if (method === "exec.approval.list") {
        return replayDeferred.promise;
      }
      return { ok: true };
    });
    const deliverRequested = vi.fn(async (request) => [{ id: request.id }]);
    const runtime = createExecApprovalChannelRuntime({
      label: "test/replay-stop-after-ready",
      clientDisplayName: "Test Replay Stop",
      cfg: {} as never,
      isConfigured: () => true,
      shouldHandle: () => true,
      deliverRequested,
      finalizeResolved: async () => undefined,
    });

    const startPromise = runtime.start();
    await vi.waitFor(() => {
      expect(mockGatewayClientRequests).toHaveBeenCalledWith("exec.approval.list", {});
    });

    const stopPromise = runtime.stop();
    replayDeferred.resolve([createExecReplayRequest()]);

    await startPromise;
    await stopPromise;

    expect(deliverRequested).not.toHaveBeenCalled();
    expect(mockGatewayClientStops).toHaveBeenCalled();
    expect(loggerMocks.error).not.toHaveBeenCalled();
  });

  it("waits for in-flight replay delivery before running stopped cleanup", async () => {
    mockReplayLists({ exec: [createExecReplayRequest()] });
    const pendingDelivery = createDeferred<Array<{ id: string }>>();
    const deliverRequested = vi.fn(async () => pendingDelivery.promise);
    const onStopped = vi.fn(async () => undefined);
    const runtime = createExecApprovalChannelRuntime({
      label: "test/replay-stop-waits",
      clientDisplayName: "Test Replay Stop Waits",
      cfg: {} as never,
      nowMs: () => 1000,
      isConfigured: () => true,
      shouldHandle: () => true,
      deliverRequested,
      finalizeResolved: async () => undefined,
      onStopped,
    });

    await runtime.start();
    await vi.waitFor(() => {
      expect(deliverRequested).toHaveBeenCalledTimes(1);
    });

    let stopResolved = false;
    const stopPromise = runtime.stop().then(() => {
      stopResolved = true;
    });
    await Promise.resolve();
    expect(stopResolved).toBe(false);
    expect(onStopped).not.toHaveBeenCalled();

    pendingDelivery.resolve([{ id: "abc" }]);
    await stopPromise;

    expect(stopResolved).toBe(true);
    expect(onStopped).toHaveBeenCalledTimes(1);
    expect(loggerMocks.error).not.toHaveBeenCalled();
  });

  it("logs replay delivery failures without failing startup", async () => {
    mockReplayLists({ exec: [createExecReplayRequest()] });
    const runtime = createExecApprovalChannelRuntime({
      label: "test/replay-error",
      clientDisplayName: "Test Replay Error",
      cfg: {} as never,
      isConfigured: () => true,
      shouldHandle: () => true,
      deliverRequested: async () => {
        throw new Error("deliver failed");
      },
      finalizeResolved: async () => undefined,
    });

    await expect(runtime.start()).resolves.toBeUndefined();

    await vi.waitFor(() => {
      expect(loggerMocks.error).toHaveBeenCalledWith(
        "error replaying pending approvals: deliver failed",
      );
    });
  });

  it("logs replay list failures without failing startup", async () => {
    mockGatewayClientRequests.mockImplementation(async (method: string) => {
      if (method === "exec.approval.list") {
        throw new Error("list failed");
      }
      return { ok: true };
    });
    const deliverRequested = vi.fn(async (request) => [{ id: request.id }]);
    const runtime = createExecApprovalChannelRuntime({
      label: "test/replay-list-error",
      clientDisplayName: "Test Replay List Error",
      cfg: {} as never,
      isConfigured: () => true,
      shouldHandle: () => true,
      deliverRequested,
      finalizeResolved: async () => undefined,
    });

    await expect(runtime.start()).resolves.toBeUndefined();

    await vi.waitFor(() => {
      expect(loggerMocks.error).toHaveBeenCalledWith(
        "error replaying pending approvals: list failed",
      );
    });
    expect(deliverRequested).not.toHaveBeenCalled();
  });

  it("clears pending state when delivery throws", async () => {
    const deliverRequested = vi
      .fn<() => Promise<Array<{ id: string }>>>()
      .mockRejectedValueOnce(new Error("deliver failed"))
      .mockResolvedValueOnce([{ id: "abc" }]);
    const finalizeResolved = vi.fn(async () => undefined);
    const runtime = createExecApprovalChannelRuntime({
      label: "test/delivery-failure",
      clientDisplayName: "Test Delivery Failure",
      cfg: {} as never,
      isConfigured: () => true,
      shouldHandle: () => true,
      deliverRequested,
      finalizeResolved,
    });

    await expect(
      runtime.handleRequested({
        id: "abc",
        request: {
          command: "echo abc",
        },
        createdAtMs: 1000,
        expiresAtMs: 2000,
      }),
    ).rejects.toThrow("deliver failed");

    await runtime.handleRequested({
      id: "abc",
      request: {
        command: "echo abc",
      },
      createdAtMs: 1000,
      expiresAtMs: 2000,
    });
    await runtime.handleResolved({
      id: "abc",
      decision: "allow-once",
      ts: 1500,
    });

    expect(finalizeResolved).toHaveBeenCalledWith({
      request: expect.objectContaining({ id: "abc" }),
      resolved: expect.objectContaining({ id: "abc", decision: "allow-once" }),
      entries: [{ id: "abc" }],
    });
  });
});

¤ Dauer der Verarbeitung: 0.30 Sekunden  (vorverarbeitet am  2026-04-27) ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.