Eine aufbereitete Darstellung der Quelle

 
     
 
 
Anforderungen  |   Konzepte  |   Entwurf  |   Entwicklung  |   Qualitätssicherung  |   Lebenszyklus  |   Steuerung
 
 
 
 

Benutzer

Quelle  approval-handler-runtime.test.ts

  Sprache: JAVA
 

import { describe, expect, it, vi } from "vitest";
import {
  createChannelApprovalHandlerFromCapability,
  createLazyChannelApprovalNativeRuntimeAdapter,
} from "./approval-handler-runtime.js";
import {
  createApprovalNativeRuntimeAdapterStubs,
  type ApprovalNativeRuntimeAdapterStubParams,
} from "./approval-handler.test-helpers.js";
import type { ExecApprovalRequest } from "./exec-approvals.js";

type ApprovalCapability = NonNullable<
  Parameters<typeof createChannelApprovalHandlerFromCapability>[0]["capability"]
>;
type ApprovalNativeAdapter = NonNullable<ApprovalCapability["native"]>;

const TEST_HANDLER_PARAMS = {
  label: "test/approval-handler",
  clientDisplayName: "Test Approval Handler",
  channel: "test",
  channelLabel: "Test",
  cfg: { channels: {} } as never,
} as const;

function makeSequentialPendingDeliveryMock() {
  return vi
    .fn()
    .mockResolvedValueOnce({ messageId: "1" })
    .mockResolvedValueOnce({ messageId: "2" });
}

function makeSequentialPendingBindingMock() {
  return vi
    .fn()
    .mockResolvedValueOnce({ bindingId: "bound-1" })
    .mockResolvedValueOnce({ bindingId: "bound-2" });
}

function makeExecApprovalRequest(id: string): ExecApprovalRequest {
  return {
    id,
    expiresAtMs: Date.now() + 60_000,
    request: {
      command: "echo hi",
      turnSourceChannel: "test",
      turnSourceTo: "origin-chat",
    },
    createdAtMs: Date.now(),
  };
}

function makeNativeApprovalCapability(
  params: {
    preferredSurface?: ReturnType<
      ApprovalNativeAdapter["describeDeliveryCapabilities"]
    >["preferredSurface"];
    supportsApproverDmSurface?: boolean;
    resolveApproverDmTargets?: ApprovalNativeAdapter["resolveApproverDmTargets"];
  } & ApprovalNativeRuntimeAdapterStubParams = {},
): ApprovalCapability {
  const preferredSurface = params.preferredSurface ?? "origin";
  return {
    native: {
      describeDeliveryCapabilities: vi.fn().mockReturnValue({
        enabled: true,
        preferredSurface,
        supportsOriginSurface: true,
        supportsApproverDmSurface: params.supportsApproverDmSurface ?? false,
        notifyOriginWhenDmOnly: false,
      }),
      resolveOriginTarget: vi.fn().mockReturnValue({ to: "origin-chat" }),
      ...(params.resolveApproverDmTargets
        ? { resolveApproverDmTargets: params.resolveApproverDmTargets }
        : {}),
    },
    nativeRuntime: createApprovalNativeRuntimeAdapterStubs(params),
  };
}

function createTestApprovalHandler(capability: ApprovalCapability) {
  return createChannelApprovalHandlerFromCapability({
    capability,
    ...TEST_HANDLER_PARAMS,
  });
}

describe("createChannelApprovalHandlerFromCapability", () => {
  it("returns null when the capability does not expose a native runtime", async () => {
    await expect(
      createChannelApprovalHandlerFromCapability({
        capability: {},
        ...TEST_HANDLER_PARAMS,
      }),
    ).resolves.toBeNull();
  });

  it("returns a runtime when the capability exposes a native runtime", async () => {
    const runtime = await createChannelApprovalHandlerFromCapability({
      capability: {
        nativeRuntime: {
          availability: {
            isConfigured: vi.fn().mockReturnValue(true),
            shouldHandle: vi.fn().mockReturnValue(true),
          },
          presentation: {
            buildPendingPayload: vi.fn(),
            buildResolvedResult: vi.fn(),
            buildExpiredResult: vi.fn(),
          },
          transport: {
            prepareTarget: vi.fn(),
            deliverPending: vi.fn(),
          },
        },
      },
      ...TEST_HANDLER_PARAMS,
    });

    expect(runtime).not.toBeNull();
  });

  it("preserves the original request and resolved approval kind when stop-time cleanup unbinds", async () => {
    const unbindPending = vi.fn();
    const runtime = await createTestApprovalHandler(
      makeNativeApprovalCapability({
        resolveApprovalKind: vi.fn().mockReturnValue("plugin"),
        unbindPending,
      }),
    );

    expect(runtime).not.toBeNull();
    const request = {
      id: "custom:1",
      expiresAtMs: Date.now() + 60_000,
      request: {
        turnSourceChannel: "test",
        turnSourceTo: "origin-chat",
      },
    } as never;

    await runtime?.handleRequested(request);
    await runtime?.stop();

    expect(unbindPending).toHaveBeenCalledWith(
      expect.objectContaining({
        request,
        approvalKind: "plugin",
      }),
    );
  });

  it("ignores duplicate pending request ids before finalization", async () => {
    const unbindPending = vi.fn();
    const buildResolvedResult = vi.fn().mockResolvedValue({ kind: "leave" });
    const runtime = await createTestApprovalHandler(
      makeNativeApprovalCapability({
        buildResolvedResult,
        deliverPending: makeSequentialPendingDeliveryMock(),
        bindPending: makeSequentialPendingBindingMock(),
        unbindPending,
      }),
    );

    expect(runtime).not.toBeNull();
    const request = makeExecApprovalRequest("exec:1");

    await runtime?.handleRequested(request);
    await runtime?.handleRequested(request);
    await runtime?.handleResolved({
      id: "exec:1",
      decision: "approved",
      resolvedBy: "operator",
    } as never);

    expect(unbindPending).toHaveBeenCalledTimes(1);
    expect(unbindPending).toHaveBeenCalledWith(
      expect.objectContaining({
        entry: { messageId: "1" },
        binding: { bindingId: "bound-1" },
        request,
      }),
    );
    expect(buildResolvedResult).toHaveBeenCalledTimes(1);
  });

  it("continues finalization cleanup after one resolved entry unbind failure", async () => {
    const unbindPending = vi
      .fn()
      .mockRejectedValueOnce(new Error("unbind failed"))
      .mockResolvedValueOnce(undefined);
    const buildResolvedResult = vi.fn().mockResolvedValue({ kind: "leave" });
    const runtime = await createTestApprovalHandler(
      makeNativeApprovalCapability({
        preferredSurface: "both",
        supportsApproverDmSurface: true,
        resolveApproverDmTargets: vi.fn().mockResolvedValue([{ to: "approver-dm" }]),
        buildResolvedResult,
        prepareTarget: vi.fn().mockImplementation(async ({ plannedTarget }) => ({
          dedupeKey: String(plannedTarget.target.to),
          target: { to: plannedTarget.target.to },
        })),
        deliverPending: makeSequentialPendingDeliveryMock(),
        bindPending: makeSequentialPendingBindingMock(),
        unbindPending,
      }),
    );

    const request = makeExecApprovalRequest("exec:2");

    await runtime?.handleRequested(request);
    await expect(
      runtime?.handleResolved({
        id: "exec:2",
        decision: "approved",
        resolvedBy: "operator",
      } as never),
    ).resolves.toBeUndefined();

    expect(unbindPending).toHaveBeenCalledTimes(2);
    expect(buildResolvedResult).toHaveBeenCalledTimes(1);
    expect(buildResolvedResult).toHaveBeenCalledWith(
      expect.objectContaining({
        entry: { messageId: "2" },
      }),
    );
  });

  it("continues stop-time unbind cleanup when one binding throws", async () => {
    const unbindPending = vi
      .fn()
      .mockRejectedValueOnce(new Error("unbind failed"))
      .mockResolvedValueOnce(undefined);
    const runtime = await createTestApprovalHandler(
      makeNativeApprovalCapability({
        deliverPending: makeSequentialPendingDeliveryMock(),
        bindPending: makeSequentialPendingBindingMock(),
        unbindPending,
      }),
    );

    const request = makeExecApprovalRequest("exec:stop-1");

    await runtime?.handleRequested(request);
    await runtime?.handleRequested({
      ...request,
      id: "exec:stop-2",
    });

    await expect(runtime?.stop()).resolves.toBeUndefined();
    expect(unbindPending).toHaveBeenCalledTimes(2);
    await expect(runtime?.stop()).resolves.toBeUndefined();
    expect(unbindPending).toHaveBeenCalledTimes(2);
  });
});

describe("createLazyChannelApprovalNativeRuntimeAdapter", () => {
  it("loads the runtime lazily and reuses the loaded adapter", async () => {
    const explicitIsConfigured = vi.fn().mockReturnValue(true);
    const explicitShouldHandle = vi.fn().mockReturnValue(false);
    const buildPendingPayload = vi.fn().mockResolvedValue({ text: "pending" });
    const load = vi.fn().mockResolvedValue({
      availability: {
        isConfigured: vi.fn(),
        shouldHandle: vi.fn(),
      },
      presentation: {
        buildPendingPayload,
        buildResolvedResult: vi.fn(),
        buildExpiredResult: vi.fn(),
      },
      transport: {
        prepareTarget: vi.fn(),
        deliverPending: vi.fn(),
      },
    });
    const adapter = createLazyChannelApprovalNativeRuntimeAdapter({
      eventKinds: ["exec"],
      isConfigured: explicitIsConfigured,
      shouldHandle: explicitShouldHandle,
      load,
    });
    const cfg = { channels: {} } as never;
    const request = { id: "exec:1" } as never;
    const view = {} as never;

    expect(adapter.eventKinds).toEqual(["exec"]);
    expect(adapter.availability.isConfigured({ cfg })).toBe(true);
    expect(adapter.availability.shouldHandle({ cfg, request })).toBe(false);
    await expect(
      adapter.presentation.buildPendingPayload({
        cfg,
        request,
        approvalKind: "exec",
        nowMs: 1,
        view,
      }),
    ).resolves.toEqual({ text: "pending" });
    expect(load).toHaveBeenCalledTimes(1);
    expect(explicitIsConfigured).toHaveBeenCalledWith({ cfg });
    expect(explicitShouldHandle).toHaveBeenCalledWith({ cfg, request });
    expect(buildPendingPayload).toHaveBeenCalledWith({
      cfg,
      request,
      approvalKind: "exec",
      nowMs: 1,
      view,
    });
  });

  it("keeps observe hooks synchronous and only uses the already-loaded runtime", async () => {
    const onDelivered = vi.fn();
    const load = vi.fn().mockResolvedValue({
      availability: {
        isConfigured: vi.fn(),
        shouldHandle: vi.fn(),
      },
      presentation: {
        buildPendingPayload: vi.fn().mockResolvedValue({ text: "pending" }),
        buildResolvedResult: vi.fn(),
        buildExpiredResult: vi.fn(),
      },
      transport: {
        prepareTarget: vi.fn(),
        deliverPending: vi.fn(),
      },
      observe: {
        onDelivered,
      },
    });
    const adapter = createLazyChannelApprovalNativeRuntimeAdapter({
      isConfigured: vi.fn().mockReturnValue(true),
      shouldHandle: vi.fn().mockReturnValue(true),
      load,
    });

    adapter.observe?.onDelivered?.({ request: { id: "exec:1" } } as never);
    expect(load).not.toHaveBeenCalled();
    expect(onDelivered).not.toHaveBeenCalled();

    await adapter.presentation.buildPendingPayload({
      cfg: {} as never,
      request: { id: "exec:1" } as never,
      approvalKind: "exec",
      nowMs: 1,
      view: {} as never,
    });
    expect(load).toHaveBeenCalledTimes(1);

    adapter.observe?.onDelivered?.({ request: { id: "exec:1" } } as never);
    expect(onDelivered).toHaveBeenCalledWith({ request: { id: "exec:1" } });
    expect(load).toHaveBeenCalledTimes(1);
  });
});

Messung V0.5 in Prozent
C=98 H=94 G=95

¤ Dauer der Verarbeitung: 0.13 Sekunden  (vorverarbeitet am  2026-06-05) ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

PVS Prover

Isabelle Prover

NIST Cobol Testsuite

Cephes Mathematical Library

Vienna Development Method

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.






                                                                                                                                                                                                                                                                                                                                                                                                     


Neuigkeiten

     Aktuelles
     Motto des Tages

Software

     Quellcodebibliothek
     Eigene Quellcodes
     Fremde Quellcodes
     Suchen

Aktivitäten

     Artikel über Sicherheit
     Anleitung zur Aktivierung von SSL

Muße

     Gedichte
     Musik
     Bilder

Jenseits des Üblichen ....
    

Besucherstatistik

Besucherstatistik