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


Quelle  manager.notify.test.ts

  Sprache: JAVA
 

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

import { describe, expect, it, vi } from "vitest";
import { createManagerHarness, FakeProvider } from "./manager.test-harness.js";

class FailFirstPlayTtsProvider extends FakeProvider {
  private failed = false;

  override async playTts(input: Parameters<FakeProvider["playTts"]>[0]): Promise<void> {
    this.playTtsCalls.push(input);
    if (!this.failed) {
      this.failed = true;
      throw new Error("synthetic tts failure");
    }
  }
}

class DelayedPlayTtsProvider extends FakeProvider {
  private releasePlayTts: (() => void) | null = null;
  private resolvePlayTtsStarted: (() => void) | null = null;
  readonly playTtsStarted = vi.fn();
  readonly playTtsStartedPromise = new Promise<void>((resolve) => {
    this.resolvePlayTtsStarted = resolve;
  });

  override async playTts(input: Parameters<FakeProvider["playTts"]>[0]): Promise<void> {
    this.playTtsCalls.push(input);
    this.playTtsStarted();
    this.resolvePlayTtsStarted?.();
    this.resolvePlayTtsStarted = null;
    await new Promise<void>((resolve) => {
      this.releasePlayTts = resolve;
    });
  }

  releaseCurrentPlayback(): void {
    this.releasePlayTts?.();
    this.releasePlayTts = null;
  }
}

function requireCall(
  manager: Awaited<ReturnType<typeof createManagerHarness>>["manager"],
  callId: string,
) {
  const call = manager.getCall(callId);
  if (!call) {
    throw new Error(`expected active call ${callId}`);
  }
  return call;
}

function requireMappedCall(
  manager: Awaited<ReturnType<typeof createManagerHarness>>["manager"],
  providerCallId: string,
) {
  const call = manager.getCallByProviderCallId(providerCallId);
  if (!call) {
    throw new Error(`expected mapped provider call ${providerCallId}`);
  }
  return call;
}

function requireFirstPlayTtsCall(provider: FakeProvider) {
  const call = provider.playTtsCalls[0];
  if (!call) {
    throw new Error("expected provider.playTts to be called once");
  }
  return call;
}

type HarnessManager = Awaited<ReturnType<typeof createManagerHarness>>["manager"];

async function waitForPlaybackDispatch() {
  await new Promise((resolve) => setTimeout(resolve, 0));
}

async function initiateCallWithMessage(
  manager: HarnessManager,
  to: string,
  message: string,
  mode: "notify" | "conversation",
) {
  const { callId, success } = await manager.initiateCall(to, undefined, { message, mode });
  expect(success).toBe(true);
  return callId;
}

async function answerCall(
  manager: HarnessManager,
  callId: string,
  eventId: string,
  providerCallId = "call-uuid",
) {
  manager.processEvent({
    id: eventId,
    type: "call.answered",
    callId,
    providerCallId,
    timestamp: Date.now(),
  });
  await waitForPlaybackDispatch();
}

function expectFirstPlayTtsText(provider: FakeProvider, text: string) {
  expect(provider.playTtsCalls).toHaveLength(1);
  expect(requireFirstPlayTtsCall(provider).text).toBe(text);
}

describe("CallManager notify and mapping", () => {
  it("upgrades providerCallId mapping when provider ID changes", async () => {
    const { manager } = await createManagerHarness();

    const { callId, success, error } = await manager.initiateCall("+15550000001");
    expect(success).toBe(true);
    expect(error).toBeUndefined();

    expect(requireCall(manager, callId).providerCallId).toBe("request-uuid");
    expect(requireMappedCall(manager, "request-uuid").callId).toBe(callId);

    manager.processEvent({
      id: "evt-1",
      type: "call.answered",
      callId,
      providerCallId: "call-uuid",
      timestamp: Date.now(),
    });

    expect(requireCall(manager, callId).providerCallId).toBe("call-uuid");
    expect(requireMappedCall(manager, "call-uuid").callId).toBe(callId);
    expect(manager.getCallByProviderCallId("request-uuid")).toBeUndefined();
  });

  it.each(["plivo", "twilio"] as const)(
    "speaks initial message on answered for notify mode (%s)",
    async (providerName) => {
      const { manager, provider } = await createManagerHarness({}, new FakeProvider(providerName));

      const callId = await initiateCallWithMessage(
        manager,
        "+15550000002",
        "Hello there",
        "notify",
      );
      await answerCall(manager, callId, `evt-2-${providerName}`);

      expectFirstPlayTtsText(provider, "Hello there");
    },
  );

  it("speaks initial message on answered for conversation mode with non-stream provider", async () => {
    const { manager, provider } = await createManagerHarness({}, new FakeProvider("plivo"));

    const callId = await initiateCallWithMessage(
      manager,
      "+15550000003",
      "Hello from conversation",
      "conversation",
    );
    await answerCall(manager, callId, "evt-conversation-plivo");

    expectFirstPlayTtsText(provider, "Hello from conversation");
  });

  it("speaks initial message on answered for conversation mode when Twilio streaming is disabled", async () => {
    const { manager, provider } = await createManagerHarness(
      { streaming: { enabled: false } },
      new FakeProvider("twilio"),
    );

    const callId = await initiateCallWithMessage(
      manager,
      "+15550000004",
      "Twilio non-stream",
      "conversation",
    );
    await answerCall(manager, callId, "evt-conversation-twilio-no-stream");

    expectFirstPlayTtsText(provider, "Twilio non-stream");
  });

  it("lets realtime conversations own the initial greeting instead of posting legacy TwiML", async () => {
    const { manager, provider } = await createManagerHarness(
      { realtime: { enabled: true, provider: "openai" } },
      new FakeProvider("twilio"),
    );

    const callId = await initiateCallWithMessage(
      manager,
      "+15550000010",
      "Tell Nana dinner is at 6pm.",
      "conversation",
    );
    await answerCall(manager, callId, "evt-conversation-twilio-realtime");

    expect(provider.playTtsCalls).toHaveLength(0);
    expect(requireCall(manager, callId).metadata).toEqual(
      expect.objectContaining({ initialMessage: "Tell Nana dinner is at 6pm." }),
    );
  });

  it("still speaks initial message in notify mode when realtime is enabled", async () => {
    const { manager, provider } = await createManagerHarness(
      { realtime: { enabled: true, provider: "openai" } },
      new FakeProvider("twilio"),
    );

    const callId = await initiateCallWithMessage(manager, "+15550000011", "Notify text", "notify");
    await answerCall(manager, callId, "evt-notify-twilio-realtime");

    expectFirstPlayTtsText(provider, "Notify text");
  });

  it("waits for stream connect in conversation mode when Twilio streaming is enabled", async () => {
    const { manager, provider } = await createManagerHarness(
      { streaming: { enabled: true } },
      new FakeProvider("twilio"),
    );

    const callId = await initiateCallWithMessage(
      manager,
      "+15550000005",
      "Twilio stream",
      "conversation",
    );
    await answerCall(manager, callId, "evt-conversation-twilio-stream");

    expect(provider.playTtsCalls).toHaveLength(0);
  });

  it("speaks on answered when Twilio streaming is enabled but stream-connect path is unavailable", async () => {
    const twilioProvider = new FakeProvider("twilio");
    twilioProvider.twilioStreamConnectEnabled = false;
    const { manager, provider } = await createManagerHarness(
      { streaming: { enabled: true } },
      twilioProvider,
    );

    const callId = await initiateCallWithMessage(
      manager,
      "+15550000009",
      "Twilio stream unavailable",
      "conversation",
    );
    await answerCall(manager, callId, "evt-conversation-twilio-stream-unavailable");

    expectFirstPlayTtsText(provider, "Twilio stream unavailable");
  });

  it("starts listening after the initial greeting for Telnyx conversation calls", async () => {
    const { manager, provider } = await createManagerHarness({}, new FakeProvider("telnyx"));

    const callId = await initiateCallWithMessage(
      manager,
      "+15550000012",
      "Telnyx hello",
      "conversation",
    );
    await answerCall(manager, callId, "evt-conversation-telnyx");

    expectFirstPlayTtsText(provider, "Telnyx hello");
    expect(provider.startListeningCalls).toEqual([
      expect.objectContaining({
        callId,
        providerCallId: "call-uuid",
      }),
    ]);
    expect(requireCall(manager, callId).state).toBe("listening");
  });

  it("preserves initialMessage after a failed first playback and retries on next trigger", async () => {
    const provider = new FailFirstPlayTtsProvider("plivo");
    const { manager } = await createManagerHarness({}, provider);

    const callId = await initiateCallWithMessage(manager, "+15550000006", "Retry me", "notify");
    await answerCall(manager, callId, "evt-retry-1");

    const afterFailure = requireCall(manager, callId);
    expect(provider.playTtsCalls).toHaveLength(1);
    expect(afterFailure.metadata).toEqual(expect.objectContaining({ initialMessage: "Retry me" }));
    expect(afterFailure.state).toBe("listening");

    await answerCall(manager, callId, "evt-retry-2");

    const afterSuccess = requireCall(manager, callId);
    expect(provider.playTtsCalls).toHaveLength(2);
    expect(afterSuccess.metadata).not.toHaveProperty("initialMessage");
  });

  it("speaks initial message only once on repeated stream-connect triggers", async () => {
    const { manager, provider } = await createManagerHarness(
      { streaming: { enabled: true } },
      new FakeProvider("twilio"),
    );

    const callId = await initiateCallWithMessage(
      manager,
      "+15550000007",
      "Stream hello",
      "conversation",
    );
    await answerCall(manager, callId, "evt-stream-answered");
    expect(provider.playTtsCalls).toHaveLength(0);

    await manager.speakInitialMessage("call-uuid");
    await manager.speakInitialMessage("call-uuid");

    expectFirstPlayTtsText(provider, "Stream hello");
  });

  it("prevents concurrent initial-message replays while first playback is in flight", async () => {
    const provider = new DelayedPlayTtsProvider("twilio");
    const { manager } = await createManagerHarness({ streaming: { enabled: true } }, provider);

    const callId = await initiateCallWithMessage(
      manager,
      "+15550000008",
      "In-flight hello",
      "conversation",
    );
    await answerCall(manager, callId, "evt-stream-answered-concurrent");
    expect(provider.playTtsCalls).toHaveLength(0);

    const first = manager.speakInitialMessage("call-uuid");
    await provider.playTtsStartedPromise;
    expect(provider.playTtsStarted).toHaveBeenCalledTimes(1);

    const second = manager.speakInitialMessage("call-uuid");
    await waitForPlaybackDispatch();
    expect(provider.playTtsCalls).toHaveLength(1);

    provider.releaseCurrentPlayback();
    await Promise.all([first, second]);

    const call = requireCall(manager, callId);
    expect(call.metadata).not.toHaveProperty("initialMessage");
    expectFirstPlayTtsText(provider, "In-flight hello");
  });
});

¤ Dauer der Verarbeitung: 0.22 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.






                                                                                                                                                                                                                                                                                                                                                                                                     


Neuigkeiten

     Aktuelles
     Motto des Tages

Software

     Produkte
     Quellcodebibliothek

Aktivitäten

     Artikel über Sicherheit
     Anleitung zur Aktivierung von SSL

Muße

     Gedichte
     Musik
     Bilder

Jenseits des Üblichen ....

Besucherstatistik

Besucherstatistik

Monitoring

Montastic status badge